From bb4a2dbba7cdb64a32e765dd72c10ae9d22d8f68 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Mon, 1 Apr 2019 22:01:40 -0400 Subject: [PATCH] Renamed directories --- include/argparse.hpp | 682 ++ test/.gitignore | 2 + test/CMakeLists.txt | 25 + test/README.md | 21 + test/catch.hpp | 15883 +++++++++++++++++++++++++++ test/main.cpp | 10 + test/test_actions.hpp | 19 + test/test_compound_arguments.hpp | 152 + test/test_container_arguments.hpp | 76 + test/test_optional_arguments.hpp | 46 + test/test_parent_parsers.hpp | 34 + test/test_parse_args.hpp | 166 + test/test_positional_arguments.hpp | 69 + 13 files changed, 17185 insertions(+) create mode 100644 include/argparse.hpp create mode 100644 test/.gitignore create mode 100644 test/CMakeLists.txt create mode 100644 test/README.md create mode 100644 test/catch.hpp create mode 100644 test/main.cpp create mode 100644 test/test_actions.hpp create mode 100644 test/test_compound_arguments.hpp create mode 100644 test/test_container_arguments.hpp create mode 100644 test/test_optional_arguments.hpp create mode 100644 test/test_parent_parsers.hpp create mode 100644 test/test_parse_args.hpp create mode 100644 test/test_positional_arguments.hpp diff --git a/include/argparse.hpp b/include/argparse.hpp new file mode 100644 index 0000000..068784a --- /dev/null +++ b/include/argparse.hpp @@ -0,0 +1,682 @@ +/* + __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ + / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ +| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse + \__,_|_| \__, | .__/ \__,_|_| |___/\___| + |___/|_| + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2019 Pranav Srinivas Kumar . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace argparse { + +// Some utility structs to check template specialization +template class Ref> +struct is_specialization : std::false_type {}; + +template class Ref, typename... Args> +struct is_specialization, Ref> : std::true_type {}; + +// Upsert into std::map +template +bool upsert(std::map& aMap, KeyType const& aKey, ElementType const& aNewValue) { + typedef typename std::map::iterator Iterator; + typedef typename std::pair Result; + Result tResult = aMap.insert(typename std::map::value_type(aKey, aNewValue)); + if (!tResult.second) { + if (!(tResult.first->second == aNewValue)) { + tResult.first->second = aNewValue; + return true; + } + else + return false; // it was the same + } + else + return true; // changed cause not existing +} + +// Check if string (haystack) starts with a substring (needle) +bool starts_with(const std::string& haystack, const std::string& needle) { + return needle.length() <= haystack.length() + && std::equal(needle.begin(), needle.end(), haystack.begin()); +}; + +// Get value at index from std::list +template +T get_from_list(const std::list& aList, size_t aIndex) { + if (aList.size() > aIndex) { + auto tIterator = aList.begin(); + std::advance(tIterator, aIndex); + return *tIterator; + } + return T(); +} + +class Argument { + friend class ArgumentParser; +public: + Argument() : + mNames({}), + mHelp(""), + mAction([](const std::string& aValue) { return aValue; }), + mValues({}), + mRawValues({}), + mNumArgs(1), + mIsOptional(false), + mIsUsed(false) {} + + Argument& help(const std::string& aHelp) { + mHelp = aHelp; + return *this; + } + + Argument& default_value(std::any aDefaultValue) { + mDefaultValue = aDefaultValue; + return *this; + } + + Argument& implicit_value(std::any aImplicitValue) { + mImplicitValue = aImplicitValue; + mNumArgs = 0; + return *this; + } + + Argument& action(std::function aAction) { + mAction = aAction; + return *this; + } + + Argument& nargs(size_t aNumArgs) { + mNumArgs = aNumArgs; + return *this; + } + + template + bool operator!=(const T& aRhs) const { + return !(*this == aRhs); + } + + // Entry point for template types other than std::vector and std::list + template + typename std::enable_if::value == false && + is_specialization::value == false, bool>::type + operator==(const T& aRhs) const { + return get() == aRhs; + } + + // Template specialization for std::vector<...> + template + typename std::enable_if::value, bool>::type + operator==(const T& aRhs) const { + T tLhs = get_vector(); + if (tLhs.size() != aRhs.size()) + return false; + else { + for (size_t i = 0; i < tLhs.size(); i++) { + auto tValueAtIndex = std::any_cast(tLhs[i]); + if (tValueAtIndex != aRhs[i]) + return false; + } + return true; + } + } + + // Template specialization for std::list<...> + template + typename std::enable_if::value, bool>::type + operator==(const T& aRhs) const { + T tLhs = get_list(); + if (tLhs.size() != aRhs.size()) + return false; + else { + for (size_t i = 0; i < tLhs.size(); i++) { + auto tValueAtIndex = std::any_cast(get_from_list(tLhs, i)); + if (tValueAtIndex != get_from_list(aRhs, i)) + return false; + } + return true; + } + } + + private: + + // Getter for template types other than std::vector and std::list + template + T get() const { + if (mValues.size() == 0) { + if (mDefaultValue.has_value()) { + return std::any_cast(mDefaultValue); + } + else + return T(); + } + else { + if (mRawValues.size() > 0) + return std::any_cast(mValues[0]); + else { + if (mDefaultValue.has_value()) + return std::any_cast(mDefaultValue); + else + return T(); + } + } + } + + // Getter for std::vector. Here T = std::vector<...> + template + T get_vector() const { + T tResult; + if (mValues.size() == 0) { + if (mDefaultValue.has_value()) { + T tDefaultValues = std::any_cast(mDefaultValue); + for (size_t i = 0; i < tDefaultValues.size(); i++) { + tResult.push_back(std::any_cast(tDefaultValues[i])); + } + return tResult; + } + else + return T(); + } + else { + if (mRawValues.size() > 0) { + for (size_t i = 0; i < mValues.size(); i++) { + tResult.push_back(std::any_cast(mValues[i])); + } + return tResult; + } + else { + if (mDefaultValue.has_value()) { + std::vector tDefaultValues = std::any_cast>(mDefaultValue); + for (size_t i = 0; i < tDefaultValues.size(); i++) { + tResult.push_back(std::any_cast(tDefaultValues[i])); + } + return tResult; + } + else + return T(); + } + } + } + + // Getter for std::list. Here T = std::list<...> + template + T get_list() const { + T tResult; + if (mValues.size() == 0) { + if (mDefaultValue.has_value()) { + T tDefaultValues = std::any_cast(mDefaultValue); + for (size_t i = 0; i < tDefaultValues.size(); i++) { + tResult.push_back(std::any_cast(get_from_list(tDefaultValues, i))); + } + return tResult; + } + else + return T(); + } + else { + if (mRawValues.size() > 0) { + for (size_t i = 0; i < mValues.size(); i++) { + tResult.push_back(std::any_cast(mValues[i])); + } + return tResult; + } + else { + if (mDefaultValue.has_value()) { + std::list tDefaultValues = std::any_cast>(mDefaultValue); + for (size_t i = 0; i < tDefaultValues.size(); i++) { + tResult.push_back(std::any_cast(get_from_list(tDefaultValues, i))); + } + return tResult; + } + else + return T(); + } + } + } + + std::vector mNames; + std::string mHelp; + std::any mDefaultValue; + std::any mImplicitValue; + std::function mAction; + std::vector mValues; + std::vector mRawValues; + size_t mNumArgs; + bool mIsOptional; + bool mIsUsed; // relevant for optional arguments. True if used by user +}; + +class ArgumentParser { + public: + ArgumentParser(const std::string& aProgramName = "") : + mProgramName(aProgramName), + mNextPositionalArgument(0) { + std::shared_ptr tArgument = std::make_shared(); + tArgument->mNames = { "-h", "--help" }; + tArgument->mHelp = "show this help message and exit"; + tArgument->mNumArgs = 0; + tArgument->mDefaultValue = false; + tArgument->mImplicitValue = true; + mOptionalArguments.push_back(tArgument); + upsert(mArgumentMap, std::string("-h"), tArgument); + upsert(mArgumentMap, std::string("--help"), tArgument); + } + + // Parameter packing + // Call add_argument with variadic number of string arguments + // TODO: enforce T to be std::string + template + Argument& add_argument(T value, Targs... Fargs) { + std::shared_ptr tArgument = std::make_shared(); + tArgument->mNames.push_back(value); + add_argument_internal(tArgument, Fargs...); + + for (auto& mName : tArgument->mNames) { + if (is_optional(mName)) + tArgument->mIsOptional = true; + } + + if (!tArgument->mIsOptional) + mPositionalArguments.push_back(tArgument); + else + mOptionalArguments.push_back(tArgument); + + for (auto& mName : tArgument->mNames) { + upsert(mArgumentMap, mName, tArgument); + } + return *tArgument; + } + + // Base case for add_parents parameter packing + void add_parents() { + for (size_t i = 0; i < mParentParsers.size(); i++) { + auto tParentParser = mParentParsers[i]; + auto tPositionalArguments = tParentParser.mPositionalArguments; + for (auto& tArgument : tPositionalArguments) { + mPositionalArguments.push_back(tArgument); + } + auto tOptionalArguments = tParentParser.mOptionalArguments; + for (auto& tArgument : tOptionalArguments) { + mOptionalArguments.push_back(tArgument); + } + auto tArgumentMap = tParentParser.mArgumentMap; + for (auto&[tKey, tValue] : tArgumentMap) { + upsert(mArgumentMap, tKey, tValue); + } + } + } + + // Parameter packed add_parents method + // Accepts a variadic number of ArgumentParser objects + template + void add_parents(T aArgumentParser, Targs... Fargs) { + mParentParsers.push_back(aArgumentParser); + add_parents(Fargs...); + } + + // Call parse_args_internal - which does all the work + // Then, validate the parsed arguments + // This variant is used mainly for testing + void parse_args(const std::vector& aArguments) { + parse_args_internal(aArguments); + parse_args_validate(); + } + + // Main entry point for parsing command-line arguments using this ArgumentParser + void parse_args(int argc, char * argv[]) { + parse_args_internal(argc, argv); + parse_args_validate(); + } + + // Getter enabled for all template types other than std::vector and std::list + template + typename std::enable_if::value == false && + is_specialization::value == false, T>::type + get(const char * aArgumentName) { + std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + if (tIterator != mArgumentMap.end()) { + return tIterator->second->get(); + } + return T(); + } + + // Getter enabled for std::vector + template + typename std::enable_if::value, T>::type + get(const char * aArgumentName) { + std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + if (tIterator != mArgumentMap.end()) { + return tIterator->second->get_vector(); + } + return T(); + } + + // Getter enabled for std::list + template + typename std::enable_if::value, T>::type + get(const char * aArgumentName) { + std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + if (tIterator != mArgumentMap.end()) { + return tIterator->second->get_list(); + } + return T(); + } + + // Indexing operator. Return a reference to an Argument object + // Used in conjuction with Argument.operator== e.g., parser["foo"] == true + Argument& operator[](const std::string& aArgumentName) { + std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + if (tIterator != mArgumentMap.end()) { + return *(tIterator->second); + } + else { + throw std::runtime_error("Argument " + aArgumentName + " not found"); + } + } + + // Printing the one and only help message + // I've stuck with a simple message format, nothing fancy. + // TODO: support user-defined help and usage messages for the ArgumentParser + std::string print_help() { + std::stringstream stream; + stream << "Usage: " << mProgramName << " [options]"; + size_t tLongestArgumentLength = get_length_of_longest_argument(); + + for (size_t i = 0; i < mPositionalArguments.size(); i++) { + auto tNames = mPositionalArguments[i]->mNames; + stream << (i == 0 ? " " : "") << tNames[0] << " "; + } + stream << "\n\n"; + + if (mPositionalArguments.size() > 0) + stream << "Positional arguments:\n"; + for (size_t i = 0; i < mPositionalArguments.size(); i++) { + size_t tCurrentLength = 0; + auto tNames = mPositionalArguments[i]->mNames; + for (size_t j = 0; j < tNames.size() - 1; j++) { + auto tCurrentName = tNames[j]; + stream << tCurrentName; + stream << ", "; + tCurrentLength += tCurrentName.length() + 2; + } + stream << tNames[tNames.size() - 1]; + tCurrentLength += tNames[tNames.size() - 1].length(); + if (tCurrentLength < tLongestArgumentLength) + stream << std::string((tLongestArgumentLength - tCurrentLength) + 2, ' '); + else if (tCurrentLength == tLongestArgumentLength) + stream << std::string(2, ' '); + else + stream << std::string((tCurrentLength - tLongestArgumentLength) + 2, ' '); + + stream << mPositionalArguments[i]->mHelp << "\n"; + } + + if (mOptionalArguments.size() > 0 && mPositionalArguments.size() > 0) + stream << "\nOptional arguments:\n"; + else if (mOptionalArguments.size() > 0) + stream << "Optional arguments:\n"; + for (size_t i = 0; i < mOptionalArguments.size(); i++) { + size_t tCurrentLength = 0; + auto tNames = mOptionalArguments[i]->mNames; + std::sort(tNames.begin(), tNames.end(), + [](const std::string& lhs, const std::string& rhs) { + return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); + }); + for (size_t j = 0; j < tNames.size() - 1; j++) { + auto tCurrentName = tNames[j]; + stream << tCurrentName; + stream << ", "; + tCurrentLength += tCurrentName.length() + 2; + } + stream << tNames[tNames.size() - 1]; + tCurrentLength += tNames[tNames.size() - 1].length(); + if (tCurrentLength < tLongestArgumentLength) + stream << std::string((tLongestArgumentLength - tCurrentLength) + 2, ' '); + else if (tCurrentLength == tLongestArgumentLength) + stream << std::string(2, ' '); + else + stream << std::string((tCurrentLength - tLongestArgumentLength) + 2, ' '); + + stream << mOptionalArguments[i]->mHelp << "\n"; + } + + std::cout << stream.str(); + return stream.str(); + } + + private: + Argument& add_argument_internal(std::shared_ptr aArgument) { + return *aArgument; + } + + template + Argument& add_argument_internal(std::shared_ptr aArgument, T aArgumentName, Targs... Fargs) { + aArgument->mNames.push_back(aArgumentName); + add_argument_internal(aArgument, Fargs...); + return *aArgument; + } + + // If an argument starts with "-" or "--", then it's optional + bool is_optional(const std::string& aName) { + return (starts_with(aName, "--") || starts_with(aName, "-")); + } + + // If the argument was defined by the user and can be found in mArgumentMap, then it's valid + bool is_valid_argument(const std::string& aName) { + std::map>::iterator tIterator = mArgumentMap.find(aName); + return (tIterator != mArgumentMap.end()); + } + + void parse_args_internal(const std::vector& aArguments) { + std::vector argv; + for (const auto& arg : aArguments) + argv.push_back((char*)arg.data()); + argv.push_back(nullptr); + return parse_args_internal(argv.size() - 1, argv.data()); + } + + void parse_args_internal(int argc, char * argv[]) { + if (mProgramName == "" && argc > 0) + mProgramName = argv[0]; + for (int i = 1; i < argc; i++) { + auto tCurrentArgument = std::string(argv[i]); + if (tCurrentArgument == "-h" || tCurrentArgument == "--help") { + print_help(); + exit(0); + } + std::map>::iterator tIterator = mArgumentMap.find(argv[i]); + if (tIterator != mArgumentMap.end()) { + // Start parsing optional argument + auto tArgument = tIterator->second; + tArgument->mIsUsed = true; + auto tCount = tArgument->mNumArgs; + + // Check to see if implicit value should be used + // Two cases to handle here: + // (1) User has explicitly programmed nargs to be 0 + // (2) User has provided an implicit value, which also sets nargs to 0 + if (tCount == 0) { + // Use implicit value for this optional argument + tArgument->mValues.push_back(tArgument->mImplicitValue); + tArgument->mRawValues.push_back(""); + tCount = 0; + } + while (tCount > 0) { + i = i + 1; + if (i < argc) { + tArgument->mRawValues.push_back(argv[i]); + if (tArgument->mAction != nullptr) + tArgument->mValues.push_back(tArgument->mAction(argv[i])); + else { + if (tArgument->mDefaultValue.has_value()) + tArgument->mValues.push_back(tArgument->mDefaultValue); + else + tArgument->mValues.push_back(std::string(argv[i])); + } + } + tCount -= 1; + } + } + else { + if (is_optional(argv[i])) { + // This is possibly a compound optional argument + // Example: We have three optional arguments -a, -u and -x + // The user provides ./main -aux ... + // Here -aux is a compound optional argument + std::string tCompoundArgument = std::string(argv[i]); + for (size_t j = 1; j < tCompoundArgument.size(); j++) { + std::string tArgument(1, tCompoundArgument[j]); + size_t tNumArgs = 0; + std::map>::iterator tIterator = mArgumentMap.find("-" + tArgument); + if (tIterator != mArgumentMap.end()) { + auto tArgumentObject = tIterator->second; + tNumArgs = tArgumentObject->mNumArgs; + } + std::vector tArgumentsForRecursiveParsing = { "", "-" + tArgument }; + while (tNumArgs > 0 && i < argc) { + i += 1; + if (i < argc) { + tArgumentsForRecursiveParsing.push_back(argv[i]); + tNumArgs -= 1; + } + } + parse_args_internal(tArgumentsForRecursiveParsing); + } + } + else { + // This is a positional argument. + // Parse and save into mPositionalArguments vector + auto tArgument = mPositionalArguments[mNextPositionalArgument]; + auto tCount = tArgument->mNumArgs - tArgument->mRawValues.size(); + while (tCount > 0) { + std::map>::iterator tIterator = mArgumentMap.find(argv[i]); + if (tIterator != mArgumentMap.end() || is_optional(argv[i])) { + i = i - 1; + break; + } + if (i < argc) { + tArgument->mRawValues.push_back(argv[i]); + if (tArgument->mAction != nullptr) + tArgument->mValues.push_back(tArgument->mAction(argv[i])); + else { + if (tArgument->mDefaultValue.has_value()) + tArgument->mValues.push_back(tArgument->mDefaultValue); + else + tArgument->mValues.push_back(std::string(argv[i])); + } + } + tCount -= 1; + if (tCount > 0) i += 1; + } + if (tCount == 0) + mNextPositionalArgument += 1; + } + } + } + } + + void parse_args_validate() { + // Check if all positional arguments are parsed + for (size_t i = 0; i < mPositionalArguments.size(); i++) { + auto tArgument = mPositionalArguments[i]; + if (tArgument->mValues.size() != tArgument->mNumArgs) { + std::cout << "error: " << tArgument->mNames[0] << ": expected " + << tArgument->mNumArgs << (tArgument->mNumArgs == 1 ? "argument. " : " arguments. ") + << tArgument->mValues.size() << " provided.\n" << std::endl; + print_help(); + exit(0); + } + } + + // Check if all user-provided optional argument values are parsed correctly + for (size_t i = 0; i < mOptionalArguments.size(); i++) { + auto tArgument = mOptionalArguments[i]; + if (tArgument->mIsUsed && tArgument->mNumArgs > 0) { + if (tArgument->mValues.size() != tArgument->mNumArgs) { + // All cool if there's a default value to return + // If no default value, then there's a problem + if (!tArgument->mDefaultValue.has_value()) { + std::cout << "error: " << tArgument->mNames[0] << ": expected " + << tArgument->mNumArgs << (tArgument->mNumArgs == 1 ? "argument. " : " arguments. ") + << tArgument->mValues.size() << " provided.\n" << std::endl; + print_help(); + exit(0); + } + } + } + else { + // TODO: check if an implicit value was programmed for this argument + } + } + } + + // Used by print_help. + size_t get_length_of_longest_argument() { + size_t tResult = 0; + for (size_t i = 0; i < mPositionalArguments.size(); i++) { + size_t tCurrentArgumentLength = 0; + auto tNames = mPositionalArguments[i]->mNames; + for (size_t j = 0; j < tNames.size() - 1; j++) { + auto tNameLength = tNames[j].length(); + tCurrentArgumentLength += tNameLength + 2; // +2 for ", " + } + tCurrentArgumentLength += tNames[tNames.size() - 1].length(); + if (tCurrentArgumentLength > tResult) + tResult = tCurrentArgumentLength; + } + + for (size_t i = 0; i < mOptionalArguments.size(); i++) { + size_t tCurrentArgumentLength = 0; + auto tNames = mOptionalArguments[i]->mNames; + for (size_t j = 0; j < tNames.size() - 1; j++) { + auto tNameLength = tNames[j].length(); + tCurrentArgumentLength += tNameLength + 2; // +2 for ", " + } + tCurrentArgumentLength += tNames[tNames.size() - 1].length(); + if (tCurrentArgumentLength > tResult) + tResult = tCurrentArgumentLength; + } + return tResult; + } + + std::string mProgramName; + std::vector mParentParsers; + std::vector> mPositionalArguments; + std::vector> mOptionalArguments; + size_t mNextPositionalArgument; + std::map> mArgumentMap; +}; + +} \ No newline at end of file diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..1fa72e7 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +build/ +build_linux/ \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..0de4790 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.6) +project(ARGPARSE) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Disable deprecation for windows +if (WIN32) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) +endif() + +# ARGPARSE executable +file(GLOB ARGPARSE_TEST_SOURCES + "*.cpp" + "*.hpp" + "../include/argparse.hpp" +) +ADD_EXECUTABLE(ARGPARSE ${ARGPARSE_TEST_SOURCES}) +INCLUDE_DIRECTORIES("../include" ".") +set_target_properties(ARGPARSE PROPERTIES OUTPUT_NAME tests) +set_property(TARGET ARGPARSE PROPERTY CXX_STANDARD 17) + +# Set ${PROJECT_NAME} as the startup project +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE) \ No newline at end of file diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..6f5a4df --- /dev/null +++ b/test/README.md @@ -0,0 +1,21 @@ +# Argparse Tests + +## Linux + +```bash +$ mkdir build +$ cd build +$ cmake ../. +$ make +$ ./tests +``` + +## Windows + +```bash +$ mkdir build +$ cd build +$ cmake ../. -G "Visual Studio 15 2017" +$ make +$ ./tests +``` diff --git a/test/catch.hpp b/test/catch.hpp new file mode 100644 index 0000000..5302844 --- /dev/null +++ b/test/catch.hpp @@ -0,0 +1,15883 @@ +/* + * Catch v2.4.1 + * Generated: 2018-09-28 15:50:15.645795 + * ---------------------------------------------------------- + * 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 4 +#define CATCH_VERSION_PATCH 1 + +#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 push +#pragma clang diagnostic ignored "-Wpadded" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif +#elif defined __GNUC__ +// GCC likes to warn on REQUIREs, and we cannot suppress them +// locally because g++'s support for _Pragma is lacking in older, +// still supported, versions +#pragma GCC diagnostic ignored "-Wparentheses" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#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 +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif +#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) || defined(__MINGW32__) +#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? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// 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) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +#define CATCH_CPP14_OR_GREATER +#endif + +#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 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") + +#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunused-variable\"") +#define CATCH_INTERNAL_UNSUPPRESS_UNUSED_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 + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +#define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#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 +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +#if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +#endif +#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 + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +#define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// 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 + +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#if defined(__clang__) && (__clang_major__ < 8) +// work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 +// fix should be in clang 8, workaround in libstdc++ 8.2 +#include +#if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +#define CATCH_CONFIG_NO_CPP17_VARIANT +#else +#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +#endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +#endif // defined(__clang__) && (__clang_major__ < 8) +#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +#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) && !defined(CATCH_INTERNAL_CONFIG_NO_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_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +#define CATCH_CONFIG_CPP11_TO_STRING +#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_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +#define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +#define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +#define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +#define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_CONFIG_DISABLE_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 +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#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; +} +} // namespace Catch + +#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); + +} // namespace Catch + +// 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 + +inline auto operator"" _catch_sr(char const *rawChars, std::size_t size) noexcept -> Catch::StringRef +{ + return Catch::StringRef(rawChars, size); +} + +// 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 + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO##__VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#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 : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF 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::StringRef(), 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 : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF 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::StringRef(), 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; } +}; +} // namespace Catch + +// end catch_stream.h + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + +#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< + !std::is_enum::value && !std::is_base_of::value, + std::string>::type +convertUnstreamable(T const &) +{ + return Detail::unprintableString; +} +template +typename std::enable_if< + !std::is_enum::value && std::is_base_of::value, + std::string>::type +convertUnstreamable(T const &ex) +{ + return ex.what(); +} + +template +typename std::enable_if< + std::is_enum::value, std::string>::type +convertUnstreamable(T const &value) +{ + return convertUnknownEnumToString(value); +} + +#if defined(_MANAGED) +//! Convert a CLR string to a utf8 std::string +template +std::string clrReferenceToString(T ^ ref) +{ + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); +} +#endif + +} // 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; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + typename std::enable_if::value, std::string>::type + convert(const Fake &value) + { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } +}; + +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)); +} + +#if defined(_MANAGED) +template +std::string stringify(T ^ e) +{ + return ::Catch::StringMaker::convert(e); +} +#endif + +} // namespace Detail + +// Some predefined specializations + +template <> +struct StringMaker +{ + static std::string convert(const std::string &str); +}; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +template <> +struct StringMaker +{ + static std::string convert(std::string_view str); +}; +#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(const std::wstring &wstr); +}; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +template <> +struct StringMaker +{ + static std::string convert(std::wstring_view str); +}; +#endif + +template <> +struct StringMaker +{ + static std::string convert(wchar_t const *str); +}; +template <> +struct StringMaker +{ + static std::string convert(wchar_t *str); +}; +#endif + +// TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, +// while keeping string semantics? +template +struct StringMaker +{ + static std::string convert(char const *str) + { + return ::Catch::Detail::stringify(std::string{str}); + } +}; +template +struct StringMaker +{ + static std::string convert(signed char const *str) + { + return ::Catch::Detail::stringify(std::string{reinterpret_cast(str)}); + } +}; +template +struct StringMaker +{ + static std::string convert(unsigned char const *str) + { + return ::Catch::Detail::stringify(std::string{reinterpret_cast(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"; + } + } +}; + +#if defined(_MANAGED) +template +struct StringMaker +{ + static std::string convert(T ^ ref) + { + return ::Catch::Detail::clrReferenceToString(ref); + } +}; +#endif + +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(); +} +} // namespace Detail + +#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_VARIANT_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(); + } +}; +} // namespace Catch +#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 &) {} +}; + +} // namespace Detail + +template +struct StringMaker> +{ + static std::string convert(const std::tuple &tuple) + { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } +}; +} // namespace Catch +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch +{ +template <> +struct StringMaker +{ + static std::string convert(const std::monostate &) + { + return "{ }"; + } +}; + +template +struct StringMaker> +{ + static std::string convert(const std::variant &variant) + { + if (variant.valueless_by_exception()) + { + return "{valueless variant}"; + } + else + { + return std::visit( + [](const auto &value) { + return ::Catch::Detail::stringify(value); + }, + variant); + } + } +}; +} // namespace Catch +#endif // CATCH_CONFIG_ENABLE_VARIANT_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; +}; + +#if defined(_MANAGED) // Managed types are never ranges +template +struct is_range +{ + static const bool value = false; +}; +#endif + +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); + } +}; +} // namespace Catch +#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 const + { + return {static_cast(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 const + { + return {static_cast(m_lhs <= rhs), m_lhs, "<=", rhs}; + } + + auto makeUnaryExpr() const -> UnaryExpr + { + return UnaryExpr{m_lhs}; + } +}; + +void handleExpression(ITransientExpression const &expr); + +template +void handleExpression(ExprLhs const &expr) +{ + handleExpression(expr.makeUnaryExpr()); +} + +struct Decomposer +{ + template + auto operator<=(T const &lhs) -> ExprLhs + { + return ExprLhs{lhs}; + } + + auto operator<=(bool value) -> ExprLhs + { + return ExprLhs{value}; + } +}; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h + +#include + +namespace Catch +{ + +class AssertionResult; +struct AssertionInfo; +struct SectionInfo; +struct SectionEndInfo; +struct MessageInfo; +struct Counts; +struct BenchmarkInfo; +struct BenchmarkStats; +struct AssertionReaction; +struct SourceLineInfo; + +struct ITransientExpression; +struct IGeneratorTracker; + +struct IResultCapture +{ + + virtual ~IResultCapture(); + + virtual bool sectionStarted(SectionInfo const §ionInfo, + Counts &assertions) = 0; + virtual void sectionEnded(SectionEndInfo const &endInfo) = 0; + virtual void sectionEndedEarly(SectionEndInfo const &endInfo) = 0; + + virtual auto acquireGeneratorTracker(SourceLineInfo const &lineInfo) -> IGeneratorTracker & = 0; + + virtual void benchmarkStarting(BenchmarkInfo const &info) = 0; + virtual void benchmarkEnded(BenchmarkStats const &stats) = 0; + + virtual void pushScopedMessage(MessageInfo const &message) = 0; + virtual void popScopedMessage(MessageInfo const &message) = 0; + + virtual void handleFatalErrorCondition(StringRef message) = 0; + + virtual void handleExpr(AssertionInfo const &info, + ITransientExpression const &expr, + AssertionReaction &reaction) = 0; + virtual void handleMessage(AssertionInfo const &info, + ResultWas::OfType resultType, + StringRef const &message, + AssertionReaction &reaction) = 0; + virtual void handleUnexpectedExceptionNotThrown(AssertionInfo const &info, + AssertionReaction &reaction) = 0; + virtual void handleUnexpectedInflightException(AssertionInfo const &info, + std::string const &message, + AssertionReaction &reaction) = 0; + virtual void handleIncomplete(AssertionInfo const &info) = 0; + virtual void handleNonExpr(AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult *getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; +}; + +IResultCapture &getResultCapture(); +} // namespace Catch + +// end catch_interfaces_capture.h +namespace Catch +{ + +struct TestFailureException +{ +}; +struct AssertionResultData; +struct IResultCapture; +class RunContext; + +class LazyExpression +{ + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const *m_transientExpression = nullptr; + bool m_isNegated; + +public: + LazyExpression(bool isNegated); + LazyExpression(LazyExpression const &other); + LazyExpression &operator=(LazyExpression const &) = delete; + + explicit operator bool() const; + + friend auto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream &; +}; + +struct AssertionReaction +{ + bool shouldDebugBreak = false; + bool shouldThrow = false; +}; + +class AssertionHandler +{ + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture &m_resultCapture; + +public: + AssertionHandler(StringRef const ¯oName, + SourceLineInfo const &lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition); + ~AssertionHandler() + { + if (!m_completed) + { + m_resultCapture.handleIncomplete(m_assertionInfo); + } + } + + template + void handleExpr(ExprLhs const &expr) + { + handleExpr(expr.makeUnaryExpr()); + } + void handleExpr(ITransientExpression const &expr); + + void handleMessage(ResultWas::OfType resultType, StringRef const &message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; +}; + +void handleExceptionMatchExpr(AssertionHandler &handler, std::string const &str, StringRef const &matcherString); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include +#include + +namespace Catch +{ + +struct MessageInfo +{ + MessageInfo(StringRef const &_macroName, + SourceLineInfo const &_lineInfo, + ResultWas::OfType _type); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator==(MessageInfo const &other) const; + bool operator<(MessageInfo const &other) const; + +private: + static unsigned int globalCount; +}; + +struct MessageStream +{ + + template + MessageStream &operator<<(T const &value) + { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; +}; + +struct MessageBuilder : MessageStream +{ + MessageBuilder(StringRef const ¯oName, + SourceLineInfo const &lineInfo, + ResultWas::OfType type); + + template + MessageBuilder &operator<<(T const &value) + { + m_stream << value; + return *this; + } + + MessageInfo m_info; +}; + +class ScopedMessage +{ +public: + explicit ScopedMessage(MessageBuilder const &builder); + ~ScopedMessage(); + + MessageInfo m_info; +}; + +class Capturer +{ + std::vector m_messages; + IResultCapture &m_resultCapture = getResultCapture(); + size_t m_captured = 0; + +public: + Capturer(StringRef macroName, SourceLineInfo const &lineInfo, ResultWas::OfType resultType, StringRef names); + ~Capturer(); + + void captureValue(size_t index, StringRef value); + + template + void captureValues(size_t index, T &&value) + { + captureValue(index, Catch::Detail::stringify(value)); + } + + template + void captureValues(size_t index, T &&value, Ts &&... values) + { + captureValues(index, value); + captureValues(index + 1, values...); + } +}; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) +#define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else +#define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH(capturer) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH(handler) \ + catch (...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT(handler) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST(macroName, resultDisposition, ...) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + INTERNAL_CATCH_TRY \ + { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr(Catch::Decomposer() <= __VA_ARGS__); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } \ + INTERNAL_CATCH_CATCH(catchAssertionHandler) \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while ((void)0, false && static_cast(!!(__VA_ARGS__))) // the expression here is never evaluated at runtime but it forces the compiler to give it a look \ + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF(macroName, resultDisposition, ...) \ + INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \ + if (Catch::getResultCapture().lastAssertionPassed()) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE(macroName, resultDisposition, ...) \ + INTERNAL_CATCH_TEST(macroName, resultDisposition, __VA_ARGS__); \ + if (!Catch::getResultCapture().lastAssertionPassed()) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW(macroName, resultDisposition, ...) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + try \ + { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch (...) \ + { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS(macroName, resultDisposition, ...) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try \ + { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch (...) \ + { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS(macroName, exceptionType, resultDisposition, expr) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try \ + { \ + static_cast(expr); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch (exceptionType const &) \ + { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch (...) \ + { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG(macroName, messageType, resultDisposition, ...) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition); \ + catchAssertionHandler.handleMessage(messageType, (Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop()).m_stream.str()); \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE(varName, macroName, ...) \ + auto varName = Catch::Capturer(macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__); \ + varName.captureValues(0, __VA_ARGS__) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO(macroName, log) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME(scopedMessage)(Catch::MessageBuilder(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info) << log); + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES(macroName, resultDisposition, matcher, ...) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try \ + { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch (...) \ + { \ + Catch::handleExceptionMatchExpr(catchAssertionHandler, matcher, #matcher##_catch_sr); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while (false) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_capture.hpp +// start catch_section.h + +// start catch_section_info.h + +// start catch_totals.h + +#include + +namespace Catch +{ + +struct Counts +{ + Counts operator-(Counts const &other) const; + Counts &operator+=(Counts const &other); + + std::size_t total() const; + bool allPassed() const; + bool allOk() const; + + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; +}; + +struct Totals +{ + + Totals operator-(Totals const &other) const; + Totals &operator+=(Totals const &other); + + Totals delta(Totals const &prevTotals) const; + + int error = 0; + Counts assertions; + Counts testCases; +}; +} // namespace Catch + +// end catch_totals.h +#include + +namespace Catch +{ + +struct SectionInfo +{ + SectionInfo(SourceLineInfo const &_lineInfo, + std::string const &_name); + + // Deprecated + SectionInfo(SourceLineInfo const &_lineInfo, + std::string const &_name, + std::string const &) : SectionInfo(_lineInfo, _name) {} + + std::string name; + std::string description; // !Deprecated: this will always be empty + SourceLineInfo lineInfo; +}; + +struct SectionEndInfo +{ + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; +}; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include + +namespace Catch +{ + +auto getCurrentNanosecondsSinceEpoch() -> uint64_t; +auto getEstimatedClockResolution() -> uint64_t; + +class Timer +{ + uint64_t m_nanoseconds = 0; + +public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; +}; + +} // namespace Catch + +// end catch_timer.h +#include + +namespace Catch +{ + +class Section : NonCopyable +{ +public: + Section(SectionInfo const &info); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + +private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; +}; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION(...) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME(catch_internal_Section) = Catch::SectionInfo(CATCH_INTERNAL_LINEINFO, __VA_ARGS__)) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION(...) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if (Catch::Section const &INTERNAL_CATCH_UNIQUE_NAME(catch_internal_Section) = Catch::SectionInfo(CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str())) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +// end catch_section.h +// start catch_benchmark.h + +#include +#include + +namespace Catch +{ + +class BenchmarkLooper +{ + + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; + + static auto getResolution() -> uint64_t; + +public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper(StringRef name) + : m_name(name), + m_resolution(getResolution()) + { + reportStart(); + m_timer.start(); + } + + explicit operator bool() + { + if (m_count < m_iterationsToRun) + return true; + return needsMoreIterations(); + } + + void increment() + { + ++m_count; + } + + void reportStart(); + auto needsMoreIterations() -> bool; +}; + +} // end namespace Catch + +#define BENCHMARK(name) \ + for (Catch::BenchmarkLooper looper(name); looper; looper.increment()) + +// end catch_benchmark.h +// start catch_interfaces_exception.h + +// start catch_interfaces_registry_hub.h + +#include +#include + +namespace Catch +{ + +class TestCase; +struct ITestCaseRegistry; +struct IExceptionTranslatorRegistry; +struct IExceptionTranslator; +struct IReporterRegistry; +struct IReporterFactory; +struct ITagAliasRegistry; +class StartupExceptionRegistry; + +using IReporterFactoryPtr = std::shared_ptr; + +struct IRegistryHub +{ + virtual ~IRegistryHub(); + + virtual IReporterRegistry const &getReporterRegistry() const = 0; + virtual ITestCaseRegistry const &getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const &getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const = 0; + + virtual StartupExceptionRegistry const &getStartupExceptionRegistry() const = 0; +}; + +struct IMutableRegistryHub +{ + virtual ~IMutableRegistryHub(); + virtual void registerReporter(std::string const &name, IReporterFactoryPtr const &factory) = 0; + virtual void registerListener(IReporterFactoryPtr const &factory) = 0; + virtual void registerTest(TestCase const &testInfo) = 0; + virtual void registerTranslator(const IExceptionTranslator *translator) = 0; + virtual void registerTagAlias(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) = 0; + virtual void registerStartupException() noexcept = 0; +}; + +IRegistryHub const &getRegistryHub(); +IMutableRegistryHub &getMutableRegistryHub(); +void cleanUp(); +std::string translateActiveException(); + +} // namespace Catch + +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG(translatorName, signature) \ + static std::string translatorName(signature) +#endif + +#include +#include +#include + +namespace Catch +{ +using exceptionTranslateFunction = std::string (*)(); + +struct IExceptionTranslator; +using ExceptionTranslators = std::vector>; + +struct IExceptionTranslator +{ + virtual ~IExceptionTranslator(); + virtual std::string translate(ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd) 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) + { + } + + std::string translate(ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd) const override + { + try + { + if (it == itEnd) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate(it + 1, itEnd); + } + 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)); + } +}; +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2(translatorName, signature) \ + static std::string translatorName(signature); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace \ + { \ + Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionRegistrar)(&translatorName); \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static std::string translatorName(signature) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION2(INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature) + +// end catch_interfaces_exception.h +// start catch_approx.h + +#include + +namespace Catch +{ +namespace Detail +{ + +class Approx +{ +private: + bool equalityComparisonImpl(double other) const; + // Validates the new margin (margin >= 0) + // out-of-line to avoid including stdexcept in the header + void setMargin(double margin); + // Validates the new epsilon (0 < epsilon < 1) + // out-of-line to avoid including stdexcept in the header + void setEpsilon(double epsilon); + +public: + explicit Approx(double value); + + static Approx custom(); + + Approx operator-() const; + + template ::value>::type> + Approx operator()(T const &value) + { + Approx approx(static_cast(value)); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template ::value>::type> + explicit Approx(T const &value) : Approx(static_cast(value)) + { + } + + template ::value>::type> + friend bool operator==(const T &lhs, Approx const &rhs) + { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>::type> + friend bool operator==(Approx const &lhs, const T &rhs) + { + return operator==(rhs, lhs); + } + + template ::value>::type> + friend bool operator!=(T const &lhs, Approx const &rhs) + { + return !operator==(lhs, rhs); + } + + template ::value>::type> + friend bool operator!=(Approx const &lhs, T const &rhs) + { + return !operator==(rhs, lhs); + } + + template ::value>::type> + friend bool operator<=(T const &lhs, Approx const &rhs) + { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator<=(Approx const &lhs, T const &rhs) + { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator>=(T const &lhs, Approx const &rhs) + { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator>=(Approx const &lhs, T const &rhs) + { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx &epsilon(T const &newEpsilon) + { + double epsilonAsDouble = static_cast(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template ::value>::type> + Approx &margin(T const &newMargin) + { + double marginAsDouble = static_cast(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template ::value>::type> + Approx &scale(T const &newScale) + { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + +private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; +}; +} // end namespace Detail + +namespace literals +{ +Detail::Approx operator"" _a(long double val); +Detail::Approx operator"" _a(unsigned long long val); +} // end namespace literals + +template <> +struct StringMaker +{ + static std::string convert(Catch::Detail::Approx const &value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include +#include + +namespace Catch +{ + +bool startsWith(std::string const &s, std::string const &prefix); +bool startsWith(std::string const &s, char prefix); +bool endsWith(std::string const &s, std::string const &suffix); +bool endsWith(std::string const &s, char 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; +}; +} // namespace Catch + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include +#include + +namespace Catch +{ +namespace Matchers +{ +namespace Impl +{ + +template +struct MatchAllOf; +template +struct MatchAnyOf; +template +struct MatchNotOf; + +class MatcherUntypedBase +{ +public: + MatcherUntypedBase() = default; + MatcherUntypedBase(MatcherUntypedBase const &) = default; + MatcherUntypedBase &operator=(MatcherUntypedBase const &) = delete; + std::string toString() const; + +protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + +template +struct MatcherMethod +{ + virtual bool match(ObjectT const &arg) const = 0; +}; +template +struct MatcherMethod +{ + virtual bool match(PtrT *arg) const = 0; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +struct MatcherBase : MatcherUntypedBase, MatcherMethod +{ + + MatchAllOf operator&&(MatcherBase const &other) const; + MatchAnyOf operator||(MatcherBase const &other) const; + MatchNotOf operator!() const; +}; + +template +struct MatchAllOf : MatcherBase +{ + bool match(ArgT const &arg) const override + { + for (auto matcher : m_matchers) + { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override + { + std::string description; + description.reserve(4 + m_matchers.size() * 32); + description += "( "; + bool first = true; + for (auto matcher : m_matchers) + { + if (first) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAllOf &operator&&(MatcherBase const &other) + { + m_matchers.push_back(&other); + return *this; + } + + std::vector const *> m_matchers; +}; +template +struct MatchAnyOf : MatcherBase +{ + + bool match(ArgT const &arg) const override + { + for (auto matcher : m_matchers) + { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override + { + std::string description; + description.reserve(4 + m_matchers.size() * 32); + description += "( "; + bool first = true; + for (auto matcher : m_matchers) + { + if (first) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf &operator||(MatcherBase const &other) + { + m_matchers.push_back(&other); + return *this; + } + + std::vector const *> m_matchers; +}; + +template +struct MatchNotOf : MatcherBase +{ + + MatchNotOf(MatcherBase const &underlyingMatcher) : m_underlyingMatcher(underlyingMatcher) {} + + bool match(ArgT const &arg) const override + { + return !m_underlyingMatcher.match(arg); + } + + std::string describe() const override + { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const &m_underlyingMatcher; +}; + +template +MatchAllOf MatcherBase::operator&&(MatcherBase const &other) const +{ + return MatchAllOf() && *this && other; +} +template +MatchAnyOf MatcherBase::operator||(MatcherBase const &other) const +{ + return MatchAnyOf() || *this || other; +} +template +MatchNotOf MatcherBase::operator!() const +{ + return MatchNotOf(*this); +} + +} // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_floating.h + +#include +#include + +namespace Catch +{ +namespace Matchers +{ + +namespace Floating +{ + +enum class FloatingPointKind : uint8_t; + +struct WithinAbsMatcher : MatcherBase +{ + WithinAbsMatcher(double target, double margin); + bool match(double const &matchee) const override; + std::string describe() const override; + +private: + double m_target; + double m_margin; +}; + +struct WithinUlpsMatcher : MatcherBase +{ + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const &matchee) const override; + std::string describe() const override; + +private: + double m_target; + int m_ulps; + FloatingPointKind m_type; +}; + +} // namespace Floating + +// The following functions create the actual matcher objects. +// This allows the types to be inferred +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); +Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include +#include + +namespace Catch +{ +namespace Matchers +{ +namespace Generic +{ + +namespace Detail +{ +std::string finalizeDescription(const std::string &desc); +} + +template +class PredicateMatcher : public MatcherBase +{ + std::function m_predicate; + std::string m_description; + +public: + PredicateMatcher(std::function const &elem, std::string const &descr) + : m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + { + } + + bool match(T const &item) const override + { + return m_predicate(item); + } + + std::string describe() const override + { + return m_description; + } +}; + +} // namespace Generic + +// The following functions create the actual matcher objects. +// The user has to explicitly specify type to the function, because +// infering std::function is hard (but possible) and +// requires a lot of TMP. +template +Generic::PredicateMatcher Predicate(std::function const &predicate, std::string const &description = "") +{ + return Generic::PredicateMatcher(predicate, description); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include + +namespace Catch +{ +namespace Matchers +{ + +namespace StdString +{ + +struct CasedString +{ + CasedString(std::string const &str, CaseSensitive::Choice caseSensitivity); + std::string adjustString(std::string const &str) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; +}; + +struct StringMatcherBase : MatcherBase +{ + StringMatcherBase(std::string const &operation, CasedString const &comparator); + std::string describe() const override; + + CasedString m_comparator; + std::string m_operation; +}; + +struct EqualsMatcher : StringMatcherBase +{ + EqualsMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; +}; +struct ContainsMatcher : StringMatcherBase +{ + ContainsMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; +}; +struct StartsWithMatcher : StringMatcherBase +{ + StartsWithMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; +}; +struct EndsWithMatcher : StringMatcherBase +{ + EndsWithMatcher(CasedString const &comparator); + bool match(std::string const &source) const override; +}; + +struct RegexMatcher : MatcherBase +{ + RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity); + bool match(std::string const &matchee) const override; + std::string describe() const override; + +private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; +}; + +} // namespace StdString + +// The following functions create the actual matcher objects. +// This allows the types to be inferred + +StdString::EqualsMatcher Equals(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); +StdString::ContainsMatcher Contains(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); +StdString::EndsWithMatcher EndsWith(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); +StdString::StartsWithMatcher StartsWith(std::string const &str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); +StdString::RegexMatcher Matches(std::string const ®ex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include + +namespace Catch +{ +namespace Matchers +{ + +namespace Vector +{ +namespace Detail +{ +template +size_t count(InputIterator first, InputIterator last, T const &item) +{ + size_t cnt = 0; + for (; first != last; ++first) + { + if (*first == item) + { + ++cnt; + } + } + return cnt; +} +template +bool contains(InputIterator first, InputIterator last, T const &item) +{ + for (; first != last; ++first) + { + if (*first == item) + { + return true; + } + } + return false; +} +} // namespace Detail + +template +struct ContainsElementMatcher : MatcherBase> +{ + + ContainsElementMatcher(T const &comparator) : m_comparator(comparator) {} + + bool match(std::vector const &v) const override + { + for (auto const &el : v) + { + if (el == m_comparator) + { + return true; + } + } + return false; + } + + std::string describe() const override + { + return "Contains: " + ::Catch::Detail::stringify(m_comparator); + } + + T const &m_comparator; +}; + +template +struct ContainsMatcher : MatcherBase> +{ + + ContainsMatcher(std::vector const &comparator) : m_comparator(comparator) {} + + bool match(std::vector const &v) const override + { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const &comparator : m_comparator) + { + auto present = false; + for (const auto &el : v) + { + if (el == comparator) + { + present = true; + break; + } + } + if (!present) + { + return false; + } + } + return true; + } + std::string describe() const override + { + return "Contains: " + ::Catch::Detail::stringify(m_comparator); + } + + std::vector const &m_comparator; +}; + +template +struct EqualsMatcher : MatcherBase> +{ + + EqualsMatcher(std::vector const &comparator) : m_comparator(comparator) {} + + bool match(std::vector const &v) const override + { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + std::string describe() const override + { + return "Equals: " + ::Catch::Detail::stringify(m_comparator); + } + std::vector const &m_comparator; +}; + +template +struct UnorderedEqualsMatcher : MatcherBase> +{ + UnorderedEqualsMatcher(std::vector const &target) : m_target(target) {} + bool match(std::vector const &vec) const override + { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include inside the common path + if (m_target.size() != vec.size()) + { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst == *rfirst) + { + ++lfirst; + ++rfirst; + } + if (lfirst == llast) + { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) + { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) + { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) + { + return false; + } + } + + return true; + } + + std::string describe() const override + { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + +private: + std::vector const &m_target; +}; + +} // namespace Vector + +// The following functions create the actual matcher objects. +// This allows the types to be inferred + +template +Vector::ContainsMatcher Contains(std::vector const &comparator) +{ + return Vector::ContainsMatcher(comparator); +} + +template +Vector::ContainsElementMatcher VectorContains(T const &comparator) +{ + return Vector::ContainsElementMatcher(comparator); +} + +template +Vector::EqualsMatcher Equals(std::vector const &comparator) +{ + return Vector::EqualsMatcher(comparator); +} + +template +Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const &target) +{ + return Vector::UnorderedEqualsMatcher(target); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch +{ + +template +class MatchExpr : public ITransientExpression +{ + ArgT const &m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + +public: + MatchExpr(ArgT const &arg, MatcherT const &matcher, StringRef const &matcherString) + : ITransientExpression{true, matcher.match(arg)}, + m_arg(arg), + m_matcher(matcher), + m_matcherString(matcherString) + { + } + + void streamReconstructedExpression(std::ostream &os) const override + { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify(m_arg) << ' '; + if (matcherAsString == Detail::unprintableString) + os << m_matcherString; + else + os << matcherAsString; + } +}; + +using StringMatcher = Matchers::Impl::MatcherBase; + +void handleExceptionMatchExpr(AssertionHandler &handler, StringMatcher const &matcher, StringRef const &matcherString); + +template +auto makeMatchExpr(ArgT const &arg, MatcherT const &matcher, StringRef const &matcherString) -> MatchExpr +{ + return MatchExpr(arg, matcher, matcherString); +} + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT(macroName, matcher, resultDisposition, arg) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition); \ + INTERNAL_CATCH_TRY \ + { \ + catchAssertionHandler.handleExpr(Catch::makeMatchExpr(arg, matcher, #matcher##_catch_sr)); \ + } \ + INTERNAL_CATCH_CATCH(catchAssertionHandler) \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while (false) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES(macroName, exceptionType, resultDisposition, matcher, ...) \ + do \ + { \ + Catch::AssertionHandler catchAssertionHandler(macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition); \ + if (catchAssertionHandler.allowThrows()) \ + try \ + { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch (exceptionType const &ex) \ + { \ + catchAssertionHandler.handleExpr(Catch::makeMatchExpr(ex, matcher, #matcher##_catch_sr)); \ + } \ + catch (...) \ + { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT(catchAssertionHandler) \ + } while (false) + +// end catch_capture_matchers.h +#endif +// start catch_generators.hpp + +// start catch_interfaces_generatortracker.h + +#include + +namespace Catch +{ + +namespace Generators +{ +class GeneratorBase +{ +protected: + size_t m_size = 0; + +public: + GeneratorBase(size_t size) : m_size(size) {} + virtual ~GeneratorBase(); + auto size() const -> size_t { return m_size; } +}; +using GeneratorBasePtr = std::unique_ptr; + +} // namespace Generators + +struct IGeneratorTracker +{ + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const & = 0; + virtual void setGenerator(Generators::GeneratorBasePtr &&generator) = 0; + virtual auto getIndex() const -> std::size_t = 0; +}; + +} // namespace Catch + +// end catch_interfaces_generatortracker.h +// start catch_enforce.h + +#include + +namespace Catch +{ +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +template +[[noreturn]] void throw_exception(Ex const &e) { + throw e; +} +#else // ^^ Exceptions are enabled // Exceptions are disabled vv +[[noreturn]] void throw_exception(std::exception const &e); +#endif +} // namespace Catch + +#define CATCH_PREPARE_EXCEPTION(type, msg) \ + type((Catch::ReusableStringStream() << msg).str()) +#define CATCH_INTERNAL_ERROR(msg) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION(std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg)) +#define CATCH_ERROR(msg) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION(std::domain_error, msg)) +#define CATCH_RUNTIME_ERROR(msg) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION(std::runtime_error, msg)) +#define CATCH_ENFORCE(condition, msg) \ + do \ + { \ + if (!(condition)) \ + CATCH_ERROR(msg); \ + } while (false) + +// end catch_enforce.h +#include +#include +#include + +#include + +namespace Catch +{ +namespace Generators +{ + +// !TBD move this into its own location? +namespace pf +{ +template +std::unique_ptr make_unique(Args &&... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} +} // namespace pf + +template +struct IGenerator +{ + virtual ~IGenerator() {} + virtual auto get(size_t index) const -> T = 0; +}; + +template +class SingleValueGenerator : public IGenerator +{ + T m_value; + +public: + SingleValueGenerator(T const &value) : m_value(value) {} + + auto get(size_t) const -> T override + { + return m_value; + } +}; + +template +class FixedValuesGenerator : public IGenerator +{ + std::vector m_values; + +public: + FixedValuesGenerator(std::initializer_list values) : m_values(values) {} + + auto get(size_t index) const -> T override + { + return m_values[index]; + } +}; + +template +class RangeGenerator : public IGenerator +{ + T const m_first; + T const m_last; + +public: + RangeGenerator(T const &first, T const &last) : m_first(first), m_last(last) + { + assert(m_last > m_first); + } + + auto get(size_t index) const -> T override + { + // ToDo:: introduce a safe cast to catch potential overflows + return static_cast(m_first + index); + } +}; + +template +struct NullGenerator : IGenerator +{ + auto get(size_t) const -> T override + { + CATCH_INTERNAL_ERROR("A Null Generator is always empty"); + } +}; + +template +class Generator +{ + std::unique_ptr> m_generator; + size_t m_size; + +public: + Generator(size_t size, std::unique_ptr> generator) + : m_generator(std::move(generator)), + m_size(size) + { + } + + auto size() const -> size_t { return m_size; } + auto operator[](size_t index) const -> T + { + assert(index < m_size); + return m_generator->get(index); + } +}; + +std::vector randomiseIndices(size_t selectionSize, size_t sourceSize); + +template +class GeneratorRandomiser : public IGenerator +{ + Generator m_baseGenerator; + + std::vector m_indices; + +public: + GeneratorRandomiser(Generator &&baseGenerator, size_t numberOfItems) + : m_baseGenerator(std::move(baseGenerator)), + m_indices(randomiseIndices(numberOfItems, m_baseGenerator.size())) + { + } + + auto get(size_t index) const -> T override + { + return m_baseGenerator[m_indices[index]]; + } +}; + +template +struct RequiresASpecialisationFor; + +template +auto all() -> Generator { return RequiresASpecialisationFor(); } + +template <> +auto all() -> Generator; + +template +auto range(T const &first, T const &last) -> Generator +{ + return Generator((last - first), pf::make_unique>(first, last)); +} + +template +auto random(T const &first, T const &last) -> Generator +{ + auto gen = range(first, last); + auto size = gen.size(); + + return Generator(size, pf::make_unique>(std::move(gen), size)); +} +template +auto random(size_t size) -> Generator +{ + return Generator(size, pf::make_unique>(all(), size)); +} + +template +auto values(std::initializer_list values) -> Generator +{ + return Generator(values.size(), pf::make_unique>(values)); +} +template +auto value(T const &val) -> Generator +{ + return Generator(1, pf::make_unique>(val)); +} + +template +auto as() -> Generator +{ + return Generator(0, pf::make_unique>()); +} + +template +auto table(std::initializer_list> &&tuples) -> Generator> +{ + return values>(std::forward>>(tuples)); +} + +template +struct Generators : GeneratorBase +{ + std::vector> m_generators; + + using type = T; + + Generators() : GeneratorBase(0) {} + + void populate(T &&val) + { + m_size += 1; + m_generators.emplace_back(value(std::move(val))); + } + template + void populate(U &&val) + { + populate(T(std::move(val))); + } + void populate(Generator &&generator) + { + m_size += generator.size(); + m_generators.emplace_back(std::move(generator)); + } + + template + void populate(U &&valueOrGenerator, Gs... moreGenerators) + { + populate(std::forward(valueOrGenerator)); + populate(std::forward(moreGenerators)...); + } + + auto operator[](size_t index) const -> T + { + size_t sizes = 0; + for (auto const &gen : m_generators) + { + auto localIndex = index - sizes; + sizes += gen.size(); + if (index < sizes) + return gen[localIndex]; + } + CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')'); + } +}; + +template +auto makeGenerators(Generator &&generator, Gs... moreGenerators) -> Generators +{ + Generators generators; + generators.m_generators.reserve(1 + sizeof...(Gs)); + generators.populate(std::move(generator), std::forward(moreGenerators)...); + return generators; +} +template +auto makeGenerators(Generator &&generator) -> Generators +{ + Generators generators; + generators.populate(std::move(generator)); + return generators; +} +template +auto makeGenerators(T &&val, Gs... moreGenerators) -> Generators +{ + return makeGenerators(value(std::forward(val)), std::forward(moreGenerators)...); +} +template +auto makeGenerators(U &&val, Gs... moreGenerators) -> Generators +{ + return makeGenerators(value(T(std::forward(val))), std::forward(moreGenerators)...); +} + +auto acquireGeneratorTracker(SourceLineInfo const &lineInfo) -> IGeneratorTracker &; + +template +// Note: The type after -> is weird, because VS2015 cannot parse +// the expression used in the typedef inside, when it is in +// return type. Yeah, ¯\_(ツ)_/¯ +auto generate(SourceLineInfo const &lineInfo, L const &generatorExpression) -> decltype(std::declval()[0]) +{ + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker &tracker = acquireGeneratorTracker(lineInfo); + if (!tracker.hasGenerator()) + tracker.setGenerator(pf::make_unique>(generatorExpression())); + + auto const &generator = static_cast const &>(*tracker.getGenerator()); + return generator[tracker.getIndex()]; +} + +} // namespace Generators +} // namespace Catch + +#define GENERATE(...) \ + Catch::Generators::generate(CATCH_INTERNAL_LINEINFO, [] { using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); }) + +// end catch_generators.hpp + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// start catch_test_case_info.h + +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch +{ + +struct ITestInvoker; + +struct TestCaseInfo +{ + enum SpecialProperties + { + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + TestCaseInfo(std::string const &_name, + std::string const &_className, + std::string const &_description, + std::vector const &_tags, + SourceLineInfo const &_lineInfo); + + friend void setTags(TestCaseInfo &testCaseInfo, std::vector tags); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string tagsAsString() const; + + std::string name; + std::string className; + std::string description; + std::vector tags; + std::vector lcaseTags; + SourceLineInfo lineInfo; + SpecialProperties properties; +}; + +class TestCase : public TestCaseInfo +{ +public: + TestCase(ITestInvoker *testCase, TestCaseInfo &&info); + + TestCase withName(std::string const &_newName) const; + + void invoke() const; + + TestCaseInfo const &getTestCaseInfo() const; + + bool operator==(TestCase const &other) const; + bool operator<(TestCase const &other) const; + +private: + std::shared_ptr test; +}; + +TestCase makeTestCase(ITestInvoker *testCase, + std::string const &className, + NameAndTags const &nameAndTags, + SourceLineInfo const &lineInfo); +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch +{ + +struct IRunner +{ + virtual ~IRunner(); + virtual bool aborting() const = 0; +}; +} // namespace Catch + +// end catch_interfaces_runner.h + +#ifdef __OBJC__ +// start catch_objc.hpp + +#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 ITestInvoker +{ + +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 ""; +} +} // namespace Detail + +inline std::size_t registerTestMethods() +{ + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList(nullptr, 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, NameAndTags(name.c_str(), desc.c_str()), SourceLineInfo("", 0))); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; +} + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + +namespace Matchers +{ +namespace Impl +{ +namespace NSStringMatchers +{ + +struct StringHolder : MatcherBase +{ + StringHolder(NSString *substr) : m_substr([substr copy]) {} + StringHolder(StringHolder const &other) : m_substr([other.m_substr copy]) {} + StringHolder() + { + arcSafeRelease(m_substr); + } + + bool match(NSString *arg) const override + { + return false; + } + + NSString *CATCH_ARC_STRONG m_substr; +}; + +struct Equals : StringHolder +{ + Equals(NSString *substr) : StringHolder(substr) {} + + bool match(NSString *str) const override + { + return (str != nil || m_substr == nil) && + [str isEqualToString:m_substr]; + } + + std::string describe() const override + { + return "equals string: " + Catch::Detail::stringify(m_substr); + } +}; + +struct Contains : StringHolder +{ + Contains(NSString *substr) : StringHolder(substr) {} + + bool match(NSString *str) const + { + return (str != nil || m_substr == nil) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + std::string describe() const override + { + return "contains string: " + Catch::Detail::stringify(m_substr); + } +}; + +struct StartsWith : StringHolder +{ + StartsWith(NSString *substr) : StringHolder(substr) {} + + bool match(NSString *str) const override + { + return (str != nil || m_substr == nil) && + [str rangeOfString:m_substr].location == 0; + } + + std::string describe() const override + { + return "starts with: " + Catch::Detail::stringify(m_substr); + } +}; +struct EndsWith : StringHolder +{ + EndsWith(NSString *substr) : StringHolder(substr) {} + + bool match(NSString *str) const override + { + return (str != nil || m_substr == nil) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + std::string describe() const override + { + return "ends with: " + Catch::Detail::stringify(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; + +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_MAKE_UNIQUE_NAME(root, uniqueSuffix) root##uniqueSuffix +#define OC_TEST_CASE2(name, desc, uniqueSuffix) \ + +(NSString *)OC_MAKE_UNIQUE_NAME(Catch_Name_test_, uniqueSuffix) \ + { \ + return @name; \ + } \ + +(NSString *)OC_MAKE_UNIQUE_NAME(Catch_Description_test_, uniqueSuffix) \ + { \ + return @desc; \ + } \ + -(void)OC_MAKE_UNIQUE_NAME(Catch_TestCase_test_, uniqueSuffix) + +#define OC_TEST_CASE(name, desc) OC_TEST_CASE2(name, desc, __LINE__) + +// end catch_objc.hpp +#endif + +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h + +// start catch_reporter_bases.hpp + +// start catch_interfaces_reporter.h + +// start catch_config.hpp + +// start catch_test_spec_parser.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_test_spec.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_wildcard_pattern.h + +namespace Catch +{ +class WildcardPattern +{ + enum WildcardPosition + { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + +public: + WildcardPattern(std::string const &pattern, CaseSensitive::Choice caseSensitivity); + virtual ~WildcardPattern() = default; + virtual bool matches(std::string const &str) const; + +private: + std::string adjustCase(std::string const &str) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; +}; +} // namespace Catch + +// end catch_wildcard_pattern.h +#include +#include +#include + +namespace Catch +{ + +class TestSpec +{ + struct Pattern + { + virtual ~Pattern(); + virtual bool matches(TestCaseInfo const &testCase) const = 0; + }; + using PatternPtr = std::shared_ptr; + + class NamePattern : public Pattern + { + public: + NamePattern(std::string const &name); + virtual ~NamePattern(); + virtual bool matches(TestCaseInfo const &testCase) const override; + + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern + { + public: + TagPattern(std::string const &tag); + virtual ~TagPattern(); + virtual bool matches(TestCaseInfo const &testCase) const override; + + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern + { + public: + ExcludedPattern(PatternPtr const &underlyingPattern); + virtual ~ExcludedPattern(); + virtual bool matches(TestCaseInfo const &testCase) const override; + + private: + PatternPtr m_underlyingPattern; + }; + + struct Filter + { + std::vector m_patterns; + + bool matches(TestCaseInfo const &testCase) const; + }; + +public: + bool hasFilters() const; + bool matches(TestCaseInfo const &testCase) const; + +private: + std::vector m_filters; + + friend class TestSpecParser; +}; +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include + +namespace Catch +{ + +struct TagAlias; + +struct ITagAliasRegistry +{ + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const *find(std::string const &alias) const = 0; + virtual std::string expandAliases(std::string const &unexpandedTestSpec) const = 0; + + static ITagAliasRegistry const &get(); +}; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch +{ + +class TestSpecParser +{ + enum Mode + { + None, + Name, + QuotedName, + Tag, + EscapedName + }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const *m_tagAliases = nullptr; + +public: + TestSpecParser(ITagAliasRegistry const &tagAliases); + + TestSpecParser &parse(std::string const &arg); + TestSpec testSpec(); + +private: + void visitChar(char c); + void startNewMode(Mode mode, std::size_t start); + void escape(); + std::string subString() const; + + template + void addPattern() + { + std::string token = subString(); + for (std::size_t i = 0; i < m_escapeChars.size(); ++i) + token = token.substr(0, m_escapeChars[i] - m_start - i) + token.substr(m_escapeChars[i] - m_start - i + 1); + m_escapeChars.clear(); + if (startsWith(token, "exclude:")) + { + m_exclusion = true; + token = token.substr(8); + } + if (!token.empty()) + { + TestSpec::PatternPtr pattern = std::make_shared(token); + if (m_exclusion) + pattern = std::make_shared(pattern); + m_currentFilter.m_patterns.push_back(pattern); + } + m_exclusion = false; + m_mode = None; + } + + void addFilter(); +}; +TestSpec parseTestSpec(std::string const &arg); + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec_parser.h +// start catch_interfaces_config.h + +#include +#include +#include +#include + +namespace Catch +{ + +enum class Verbosity +{ + Quiet = 0, + Normal, + High +}; + +struct WarnAbout +{ + enum What + { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; +}; + +struct ShowDurations +{ + enum OrNot + { + DefaultForReporter, + Always, + Never + }; +}; +struct RunTests +{ + enum InWhatOrder + { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; +}; +struct UseColour +{ + enum YesOrNo + { + Auto, + Yes, + No + }; +}; +struct WaitForKeypress +{ + enum When + { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; +}; + +class TestSpec; + +struct IConfig : NonCopyable +{ + + 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 bool warnAboutNoTests() 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 bool hasTestFilters() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const &getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; +}; + +using IConfigPtr = std::shared_ptr; +} // namespace Catch + +// end catch_interfaces_config.h +// Libstdc++ doesn't like incomplete classes for unique_ptr + +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch +{ + +struct IStream; + +struct ConfigData +{ + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER + + std::vector testsOrTags; + std::vector sectionsToRun; +}; + +class Config : public IConfig +{ +public: + Config() = default; + Config(ConfigData const &data); + virtual ~Config() = default; + + std::string const &getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const &getReporterName() const; + + std::vector const &getTestsOrTags() const; + std::vector const &getSectionsToRun() const override; + + virtual TestSpec const &testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream &stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + +private: + IStream const *openStream(); + ConfigData m_data; + + std::unique_ptr m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; +}; + +} // end namespace Catch + +// end catch_config.hpp +// start catch_assertionresult.h + +#include + +namespace Catch +{ + +struct AssertionResultData +{ + AssertionResultData() = delete; + + AssertionResultData(ResultWas::OfType _resultType, LazyExpression const &_lazyExpression); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; +}; + +class AssertionResult +{ +public: + AssertionResult() = delete; + AssertionResult(AssertionInfo const &info, AssertionResultData const &data); + + 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; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; +}; + +} // end namespace Catch + +// end catch_assertionresult.h +// start catch_option.hpp + +namespace Catch +{ + +// An optional type +template +class Option +{ +public: + Option() : nullableValue(nullptr) {} + Option(T const &_value) + : nullableValue(new (storage) T(_value)) + { + } + Option(Option const &_other) + : nullableValue(_other ? new (storage) T(*_other) : nullptr) + { + } + + ~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 = nullptr; + } + + 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 != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator!() const { return nullableValue == nullptr; } + explicit operator bool() const + { + return some(); + } + +private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; +}; + +} // end namespace Catch + +// end catch_option.hpp +#include +#include +#include +#include +#include + +namespace Catch +{ + +struct ReporterConfig +{ + explicit ReporterConfig(IConfigPtr const &_fullConfig); + + ReporterConfig(IConfigPtr const &_fullConfig, std::ostream &_stream); + + std::ostream &stream() const; + IConfigPtr fullConfig() const; + +private: + std::ostream *m_stream; + IConfigPtr m_fullConfig; +}; + +struct ReporterPreferences +{ + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; +}; + +template +struct LazyStat : Option +{ + LazyStat &operator=(T const &_value) + { + Option::operator=(_value); + used = false; + return *this; + } + void reset() + { + Option::reset(); + used = false; + } + bool used = false; +}; + +struct TestRunInfo +{ + TestRunInfo(std::string const &_name); + std::string name; +}; +struct GroupInfo +{ + GroupInfo(std::string const &_name, + std::size_t _groupIndex, + std::size_t _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); + + AssertionStats(AssertionStats const &) = default; + AssertionStats(AssertionStats &&) = default; + AssertionStats &operator=(AssertionStats const &) = default; + AssertionStats &operator=(AssertionStats &&) = default; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; +}; + +struct SectionStats +{ + SectionStats(SectionInfo const &_sectionInfo, + Counts const &_assertions, + double _durationInSeconds, + bool _missingAssertions); + SectionStats(SectionStats const &) = default; + SectionStats(SectionStats &&) = default; + SectionStats &operator=(SectionStats const &) = default; + SectionStats &operator=(SectionStats &&) = default; + virtual ~SectionStats(); + + 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); + + TestCaseStats(TestCaseStats const &) = default; + TestCaseStats(TestCaseStats &&) = default; + TestCaseStats &operator=(TestCaseStats const &) = default; + TestCaseStats &operator=(TestCaseStats &&) = default; + virtual ~TestCaseStats(); + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; +}; + +struct TestGroupStats +{ + TestGroupStats(GroupInfo const &_groupInfo, + Totals const &_totals, + bool _aborting); + TestGroupStats(GroupInfo const &_groupInfo); + + TestGroupStats(TestGroupStats const &) = default; + TestGroupStats(TestGroupStats &&) = default; + TestGroupStats &operator=(TestGroupStats const &) = default; + TestGroupStats &operator=(TestGroupStats &&) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; +}; + +struct TestRunStats +{ + TestRunStats(TestRunInfo const &_runInfo, + Totals const &_totals, + bool _aborting); + + TestRunStats(TestRunStats const &) = default; + TestRunStats(TestRunStats &&) = default; + TestRunStats &operator=(TestRunStats const &) = default; + TestRunStats &operator=(TestRunStats &&) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; +}; + +struct BenchmarkInfo +{ + std::string name; +}; +struct BenchmarkStats +{ + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; +}; + +struct IStreamingReporter +{ + virtual ~IStreamingReporter() = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set getSupportedVerbosities() + + 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 §ionInfo) = 0; + + // *** experimental *** + virtual void benchmarkStarting(BenchmarkInfo const &) {} + + 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; + + // *** experimental *** + virtual void benchmarkEnded(BenchmarkStats const &) {} + + virtual void sectionEnded(SectionStats const §ionStats) = 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; + + // Default empty implementation provided + virtual void fatalErrorEncountered(StringRef name); + + virtual bool isMulti() const; +}; +using IStreamingReporterPtr = std::unique_ptr; + +struct IReporterFactory +{ + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create(ReporterConfig const &config) const = 0; + virtual std::string getDescription() const = 0; +}; +using IReporterFactoryPtr = std::shared_ptr; + +struct IReporterRegistry +{ + using FactoryMap = std::map; + using Listeners = std::vector; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create(std::string const &name, IConfigPtr const &config) const = 0; + virtual FactoryMap const &getFactories() const = 0; + virtual Listeners const &getListeners() const = 0; +}; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include +#include +#include +#include +#include +#include +#include + +namespace Catch +{ +void prepareExpandedExpression(AssertionResult &result); + +// Returns double formatted as %.3f (format expected on output) +std::string getFormattedDuration(double duration); + +template +struct StreamingReporterBase : IStreamingReporter +{ + + StreamingReporterBase(ReporterConfig const &_config) + : m_config(_config.fullConfig()), + stream(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if (!DerivedT::getSupportedVerbosities().count(m_config->verbosity())) + CATCH_ERROR("Verbosity level not supported by this reporter"); + } + + ReporterPreferences getPreferences() const override + { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() + { + return {Verbosity::Normal}; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const &) override {} + + void testRunStarting(TestRunInfo const &_testRunInfo) override + { + currentTestRunInfo = _testRunInfo; + } + void testGroupStarting(GroupInfo const &_groupInfo) override + { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const &_testInfo) override + { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const &_sectionInfo) override + { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const & /* _sectionStats */) override + { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const & /* _testCaseStats */) override + { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const & /* _testGroupStats */) override + { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const & /* _testRunStats */) override + { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const &) override + { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream &stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; +}; + +template +struct CumulativeReporterBase : IStreamingReporter +{ + template + struct Node + { + explicit Node(T const &_value) : value(_value) {} + virtual ~Node() {} + + using ChildNodes = std::vector>; + T value; + ChildNodes children; + }; + struct SectionNode + { + explicit SectionNode(SectionStats const &_stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator==(SectionNode const &other) const + { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator==(std::shared_ptr const &other) const + { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector>; + using Assertions = std::vector; + 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()(std::shared_ptr const &node) const + { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const &) = delete; + + private: + SectionInfo const &m_other; + }; + + using TestCaseNode = Node; + using TestGroupNode = Node; + using TestRunNode = Node; + + CumulativeReporterBase(ReporterConfig const &_config) + : m_config(_config.fullConfig()), + stream(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if (!DerivedT::getSupportedVerbosities().count(m_config->verbosity())) + CATCH_ERROR("Verbosity level not supported by this reporter"); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override + { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() + { + return {Verbosity::Normal}; + } + + void testRunStarting(TestRunInfo const &) override {} + void testGroupStarting(GroupInfo const &) override {} + + void testCaseStarting(TestCaseInfo const &) override {} + + void sectionStarting(SectionInfo const §ionInfo) override + { + SectionStats incompleteStats(sectionInfo, Counts(), 0, false); + std::shared_ptr node; + if (m_sectionStack.empty()) + { + if (!m_rootSection) + m_rootSection = std::make_shared(incompleteStats); + node = m_rootSection; + } + else + { + SectionNode &parentNode = *m_sectionStack.back(); + auto it = + std::find_if(parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo(sectionInfo)); + if (it == parentNode.childSections.end()) + { + node = std::make_shared(incompleteStats); + parentNode.childSections.push_back(node); + } + else + node = *it; + } + m_sectionStack.push_back(node); + m_deepestSection = std::move(node); + } + + void assertionStarting(AssertionInfo const &) override {} + + bool assertionEnded(AssertionStats const &assertionStats) override + { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast(assertionStats.assertionResult)); + SectionNode §ionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const §ionStats) override + { + assert(!m_sectionStack.empty()); + SectionNode &node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const &testCaseStats) override + { + auto node = std::make_shared(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; + } + void testGroupEnded(TestGroupStats const &testGroupStats) override + { + auto node = std::make_shared(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const &testRunStats) override + { + auto node = std::make_shared(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const &) override {} + + IConfigPtr 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; + + std::shared_ptr m_rootSection; + std::shared_ptr m_deepestSection; + std::vector> m_sectionStack; + ReporterPreferences m_reporterPrefs; +}; + +template +char const *getLineOfChars() +{ + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if (!*line) + { + std::memset(line, C, CATCH_CONFIG_CONSOLE_WIDTH - 1); + line[CATCH_CONFIG_CONSOLE_WIDTH - 1] = 0; + } + return line; +} + +struct TestEventListenerBase : StreamingReporterBase +{ + TestEventListenerBase(ReporterConfig const &_config); + + void assertionStarting(AssertionInfo const &) override; + bool assertionEnded(AssertionStats const &) override; +}; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +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, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour(Code _colourCode); + Colour(Colour &&other) noexcept; + Colour &operator=(Colour &&other) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use(Code _colourCode); + +private: + bool m_moved = false; +}; + +std::ostream &operator<<(std::ostream &os, Colour const &); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + +namespace Catch +{ + +template +class ReporterRegistrar +{ + + class ReporterFactory : public IReporterFactory + { + + virtual IStreamingReporterPtr create(ReporterConfig const &config) const override + { + return std::unique_ptr(new T(config)); + } + + virtual std::string getDescription() const override + { + return T::getDescription(); + } + }; + +public: + explicit ReporterRegistrar(std::string const &name) + { + getMutableRegistryHub().registerReporter(name, std::make_shared()); + } +}; + +template +class ListenerRegistrar +{ + + class ListenerFactory : public IReporterFactory + { + + virtual IStreamingReporterPtr create(ReporterConfig const &config) const override + { + return std::unique_ptr(new T(config)); + } + virtual std::string getDescription() const override + { + return std::string(); + } + }; + +public: + ListenerRegistrar() + { + getMutableRegistryHub().registerListener(std::make_shared()); + } +}; +} // namespace Catch + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER(name, reporterType) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace \ + { \ + Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType(name); \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +#define CATCH_REGISTER_LISTENER(listenerType) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace \ + { \ + Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; \ + } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch +{ + +struct CompactReporter : StreamingReporterBase +{ + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const &spec) override; + + void assertionStarting(AssertionInfo const &) override; + + bool assertionEnded(AssertionStats const &_assertionStats) override; + + void sectionEnded(SectionStats const &_sectionStats) override; + + void testRunEnded(TestRunStats const &_testRunStats) override; +}; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch \ + // Note that 4062 (not all labels are handled \ + // and default is missing) is enabled +#endif + +namespace Catch +{ +// Fwd decls +struct SummaryColumn; +class TablePrinter; + +struct ConsoleReporter : StreamingReporterBase +{ + std::unique_ptr m_tablePrinter; + + ConsoleReporter(ReporterConfig const &config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const &spec) override; + + void assertionStarting(AssertionInfo const &) override; + + bool assertionEnded(AssertionStats const &_assertionStats) override; + + void sectionStarting(SectionInfo const &_sectionInfo) override; + void sectionEnded(SectionStats const &_sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const &info) override; + void benchmarkEnded(BenchmarkStats const &stats) override; + + void testCaseEnded(TestCaseStats const &_testCaseStats) override; + void testGroupEnded(TestGroupStats const &_testGroupStats) override; + void testRunEnded(TestRunStats const &_testRunStats) override; + +private: + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const &_name); + void printOpenHeader(std::string const &_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); + + void printTotals(Totals const &totals); + void printSummaryRow(std::string const &label, std::vector const &cols, std::size_t row); + + void printTotalsDivider(Totals const &totals); + void printSummaryDivider(); + +private: + bool m_headerPrinted = false; +}; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include + +namespace Catch +{ + +class XmlEncode +{ +public: + enum ForWhat + { + ForTextNodes, + ForAttributes + }; + + XmlEncode(std::string const &str, ForWhat forWhat = ForTextNodes); + + void encodeTo(std::ostream &os) const; + + friend std::ostream &operator<<(std::ostream &os, XmlEncode const &xmlEncode); + +private: + std::string m_str; + ForWhat m_forWhat; +}; + +class XmlWriter +{ +public: + class ScopedElement + { + public: + ScopedElement(XmlWriter *writer); + + ScopedElement(ScopedElement &&other) noexcept; + ScopedElement &operator=(ScopedElement &&other) noexcept; + + ~ScopedElement(); + + ScopedElement &writeText(std::string const &text, bool indent = true); + + template + ScopedElement &writeAttribute(std::string const &name, T const &attribute) + { + m_writer->writeAttribute(name, attribute); + return *this; + } + + private: + mutable XmlWriter *m_writer = nullptr; + }; + + XmlWriter(std::ostream &os = Catch::cout()); + ~XmlWriter(); + + XmlWriter(XmlWriter const &) = delete; + XmlWriter &operator=(XmlWriter const &) = delete; + + XmlWriter &startElement(std::string const &name); + + ScopedElement scopedElement(std::string const &name); + + XmlWriter &endElement(); + + XmlWriter &writeAttribute(std::string const &name, std::string const &attribute); + + XmlWriter &writeAttribute(std::string const &name, bool attribute); + + template + XmlWriter &writeAttribute(std::string const &name, T const &attribute) + { + ReusableStringStream rss; + rss << attribute; + return writeAttribute(name, rss.str()); + } + + XmlWriter &writeText(std::string const &text, bool indent = true); + + XmlWriter &writeComment(std::string const &text); + + void writeStylesheetRef(std::string const &url); + + XmlWriter &writeBlankLine(); + + void ensureTagClosed(); + +private: + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream &m_os; +}; + +} // namespace Catch + +// end catch_xmlwriter.h +namespace Catch +{ + +class JunitReporter : public CumulativeReporterBase +{ +public: + JunitReporter(ReporterConfig const &_config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const & /*spec*/) override; + + void testRunStarting(TestRunInfo const &runInfo) override; + + void testGroupStarting(GroupInfo const &groupInfo) override; + + void testCaseStarting(TestCaseInfo const &testCaseInfo) override; + bool assertionEnded(AssertionStats const &assertionStats) override; + + void testCaseEnded(TestCaseStats const &testCaseStats) override; + + void testGroupEnded(TestGroupStats const &testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const &groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const &testCaseNode); + + void writeSection(std::string const &className, + std::string const &rootName, + SectionNode const §ionNode); + + void writeAssertions(SectionNode const §ionNode); + void writeAssertion(AssertionStats const &stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; +}; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch +{ +class XmlReporter : public StreamingReporterBase +{ +public: + XmlReporter(ReporterConfig const &_config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const &sourceInfo); + +public: // StreamingReporterBase + void noMatchingTestCases(std::string const &s) override; + + void testRunStarting(TestRunInfo const &testInfo) override; + + void testGroupStarting(GroupInfo const &groupInfo) override; + + void testCaseStarting(TestCaseInfo const &testInfo) override; + + void sectionStarting(SectionInfo const §ionInfo) override; + + void assertionStarting(AssertionInfo const &) override; + + bool assertionEnded(AssertionStats const &assertionStats) override; + + void sectionEnded(SectionStats const §ionStats) override; + + void testCaseEnded(TestCaseStats const &testCaseStats) override; + + void testGroupEnded(TestGroupStats const &testGroupStats) override; + + void testRunEnded(TestRunStats const &testRunStats) override; + +private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; +}; + +} // end namespace Catch + +// end catch_reporter_xml.h + +// end catch_external_interfaces.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include +#include +#include + +namespace Catch +{ +namespace TestCaseTracking +{ + +struct NameAndLocation +{ + std::string name; + SourceLineInfo location; + + NameAndLocation(std::string const &_name, SourceLineInfo const &_location); +}; + +struct ITracker; + +using ITrackerPtr = std::shared_ptr; + +struct ITracker +{ + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const &nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker &parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild(ITrackerPtr const &child) = 0; + virtual ITrackerPtr findChild(NameAndLocation const &nameAndLocation) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; +}; + +class TrackerContext +{ + + enum RunState + { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker *m_currentTracker = nullptr; + RunState m_runState = NotStarted; + +public: + static TrackerContext &instance(); + + ITracker &startRun(); + void endRun(); + + void startCycle(); + void completeCycle(); + + bool completedCycle() const; + ITracker ¤tTracker(); + void setCurrentTracker(ITracker *tracker); +}; + +class TrackerBase : public ITracker +{ +protected: + enum CycleState + { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + using Children = std::vector; + NameAndLocation m_nameAndLocation; + TrackerContext &m_ctx; + ITracker *m_parent; + Children m_children; + CycleState m_runState = NotStarted; + +public: + TrackerBase(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent); + + NameAndLocation const &nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + + void addChild(ITrackerPtr const &child) override; + + ITrackerPtr findChild(NameAndLocation const &nameAndLocation) override; + ITracker &parent() override; + + void openChild() override; + + bool isSectionTracker() const override; + bool isIndexTracker() const override; + + void open(); + + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; + +private: + void moveToParent(); + void moveToThis(); +}; + +class SectionTracker : public TrackerBase +{ + std::vector m_filters; + +public: + SectionTracker(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent); + + bool isSectionTracker() const override; + + static SectionTracker &acquire(TrackerContext &ctx, NameAndLocation const &nameAndLocation); + + void tryOpen(); + + void addInitialFilters(std::vector const &filters); + void addNextFilters(std::vector const &filters); +}; + +class IndexTracker : public TrackerBase +{ + int m_size; + int m_index = -1; + +public: + IndexTracker(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent, int size); + + bool isIndexTracker() const override; + void close() override; + + static IndexTracker &acquire(TrackerContext &ctx, NameAndLocation const &nameAndLocation, int size); + + int index() const; + + void moveNext(); +}; + +} // namespace TestCaseTracking + +using TestCaseTracking::IndexTracker; +using TestCaseTracking::ITracker; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::TrackerContext; + +} // namespace Catch + +// end catch_test_case_tracker.h + +// start catch_leak_detector.h + +namespace Catch +{ + +struct LeakDetector +{ + LeakDetector(); +}; + +} // namespace Catch +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include +#include + +namespace +{ + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) +{ + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} // namespace + +namespace Catch +{ +namespace Detail +{ + +Approx::Approx(double value) + : m_epsilon(std::numeric_limits::epsilon() * 100), + m_margin(0.0), + m_scale(0.0), + m_value(value) +{ +} + +Approx Approx::custom() +{ + return Approx(0); +} + +Approx Approx::operator-() const +{ + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; +} + +std::string Approx::toString() const +{ + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify(m_value) << " )"; + return rss.str(); +} + +bool Approx::equalityComparisonImpl(const double other) const +{ + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); +} + +void Approx::setMargin(double margin) +{ + CATCH_ENFORCE(margin >= 0, + "Invalid Approx::margin: " << margin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = margin; +} + +void Approx::setEpsilon(double epsilon) +{ + CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0, + "Invalid Approx::epsilon: " << epsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = epsilon; +} + +} // end namespace Detail + +namespace literals +{ +Detail::Approx operator"" _a(long double val) +{ + return Detail::Approx(val); +} +Detail::Approx operator"" _a(unsigned long long val) +{ + return Detail::Approx(val); +} +} // end namespace literals + +std::string StringMaker::convert(Catch::Detail::Approx const &value) +{ + return value.toString(); +} + +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp + +// start catch_context.h + +#include + +namespace Catch +{ + +struct IResultCapture; +struct IRunner; +struct IConfig; +struct IMutableContext; + +using IConfigPtr = std::shared_ptr; + +struct IContext +{ + virtual ~IContext(); + + virtual IResultCapture *getResultCapture() = 0; + virtual IRunner *getRunner() = 0; + virtual IConfigPtr const &getConfig() const = 0; +}; + +struct IMutableContext : IContext +{ + virtual ~IMutableContext(); + virtual void setResultCapture(IResultCapture *resultCapture) = 0; + virtual void setRunner(IRunner *runner) = 0; + virtual void setConfig(IConfigPtr const &config) = 0; + +private: + static IMutableContext *currentContext; + friend IMutableContext &getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); +}; + +inline IMutableContext &getCurrentMutableContext() +{ + if (!IMutableContext::currentContext) + IMutableContext::createContext(); + return *IMutableContext::currentContext; +} + +inline IContext &getCurrentContext() +{ + return getCurrentMutableContext(); +} + +void cleanUpContext(); +} // namespace Catch + +// end catch_context.h +// start catch_debugger.h + +namespace Catch +{ +bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + +#define CATCH_TRAP() __asm__("int $3\n" \ + : \ + :) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) +// If we can use inline assembler, do it because this allows us to break +// directly at the location of the failing check instead of breaking inside +// raise() called from it, i.e. one stack frame below. +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +#define CATCH_TRAP() asm volatile("int $3") /* NOLINT */ +#else // Fall back to the generic way. +#include + +#define CATCH_TRAP() raise(SIGTRAP) +#endif +#elif defined(_MSC_VER) +#define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +#define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP +#define CATCH_BREAK_INTO_DEBUGGER() \ + if (Catch::isDebuggerActive()) \ + { \ + CATCH_TRAP(); \ + } +#else +namespace Catch +{ +inline void doNothing() {} +} // namespace Catch +#define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +// start catch_windows_h_proxy.h + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +#define CATCH_DEFINED_NOMINMAX +#define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +#define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +#undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h +#if defined(CATCH_CONFIG_WINDOWS_SEH) + +namespace Catch +{ + +struct FatalConditionHandler +{ + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + +private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; +}; + +} // namespace Catch + +#elif defined(CATCH_CONFIG_POSIX_SIGNALS) + +#include + +namespace Catch +{ + +struct FatalConditionHandler +{ + + static bool isSet; + static struct sigaction oldSigActions[]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal(int sig); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); +}; + +} // namespace Catch + +#else + +namespace Catch +{ +struct FatalConditionHandler +{ + void reset(); +}; +} // namespace Catch + +#endif + +// end catch_fatal_condition.h +#include + +namespace Catch +{ + +struct IMutableContext; + +/////////////////////////////////////////////////////////////////////////// + +class RunContext : public IResultCapture, public IRunner +{ + +public: + RunContext(RunContext const &) = delete; + RunContext &operator=(RunContext const &) = delete; + + explicit RunContext(IConfigPtr const &_config, IStreamingReporterPtr &&reporter); + + ~RunContext() override; + + void testGroupStarting(std::string const &testSpec, std::size_t groupIndex, std::size_t groupsCount); + void testGroupEnded(std::string const &testSpec, Totals const &totals, std::size_t groupIndex, std::size_t groupsCount); + + Totals runTest(TestCase const &testCase); + + IConfigPtr config() const; + IStreamingReporter &reporter() const; + +public: // IResultCapture + // Assertion handlers + void handleExpr(AssertionInfo const &info, + ITransientExpression const &expr, + AssertionReaction &reaction) override; + void handleMessage(AssertionInfo const &info, + ResultWas::OfType resultType, + StringRef const &message, + AssertionReaction &reaction) override; + void handleUnexpectedExceptionNotThrown(AssertionInfo const &info, + AssertionReaction &reaction) override; + void handleUnexpectedInflightException(AssertionInfo const &info, + std::string const &message, + AssertionReaction &reaction) override; + void handleIncomplete(AssertionInfo const &info) override; + void handleNonExpr(AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction) override; + + bool sectionStarted(SectionInfo const §ionInfo, Counts &assertions) override; + + void sectionEnded(SectionEndInfo const &endInfo) override; + void sectionEndedEarly(SectionEndInfo const &endInfo) override; + + auto acquireGeneratorTracker(SourceLineInfo const &lineInfo) -> IGeneratorTracker & override; + + void benchmarkStarting(BenchmarkInfo const &info) override; + void benchmarkEnded(BenchmarkStats const &stats) override; + + void pushScopedMessage(MessageInfo const &message) override; + void popScopedMessage(MessageInfo const &message) override; + + std::string getCurrentTestName() const override; + + const AssertionResult *getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition(StringRef message) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + +public: + // !TBD We need to do this another way! + bool aborting() const final; + +private: + void runCurrentTest(std::string &redirectedCout, std::string &redirectedCerr); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions(Counts &assertions); + + void assertionEnded(AssertionResult const &result); + void reportExpr(AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated); + + void populateReaction(AssertionReaction &reaction); + +private: + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext &m_context; + TestCase const *m_activeTestCase = nullptr; + ITracker *m_testCaseTracker; + Option m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; +}; + +} // end namespace Catch + +// end catch_run_context.h +namespace Catch +{ + +namespace +{ +auto operator<<(std::ostream &os, ITransientExpression const &expr) -> std::ostream & +{ + expr.streamReconstructedExpression(os); + return os; +} +} // namespace + +LazyExpression::LazyExpression(bool isNegated) + : m_isNegated(isNegated) +{ +} + +LazyExpression::LazyExpression(LazyExpression const &other) : m_isNegated(other.m_isNegated) {} + +LazyExpression::operator bool() const +{ + return m_transientExpression != nullptr; +} + +auto operator<<(std::ostream &os, LazyExpression const &lazyExpr) -> std::ostream & +{ + if (lazyExpr.m_isNegated) + os << "!"; + + if (lazyExpr) + { + if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression()) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } + else + { + os << "{** error - unchecked empty expression requested **}"; + } + return os; +} + +AssertionHandler::AssertionHandler(StringRef const ¯oName, + SourceLineInfo const &lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition) + : m_assertionInfo{macroName, lineInfo, capturedExpression, resultDisposition}, + m_resultCapture(getResultCapture()) +{ +} + +void AssertionHandler::handleExpr(ITransientExpression const &expr) +{ + m_resultCapture.handleExpr(m_assertionInfo, expr, m_reaction); +} +void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const &message) +{ + m_resultCapture.handleMessage(m_assertionInfo, resultType, message, m_reaction); +} + +auto AssertionHandler::allowThrows() const -> bool +{ + return getCurrentContext().getConfig()->allowThrows(); +} + +void AssertionHandler::complete() +{ + setCompleted(); + if (m_reaction.shouldDebugBreak) + { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) + { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestFailureException(); +#else + CATCH_ERROR("Test failure requires aborting test!"); +#endif + } +} +void AssertionHandler::setCompleted() +{ + m_completed = true; +} + +void AssertionHandler::handleUnexpectedInflightException() +{ + m_resultCapture.handleUnexpectedInflightException(m_assertionInfo, Catch::translateActiveException(), m_reaction); +} + +void AssertionHandler::handleExceptionThrownAsExpected() +{ + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); +} +void AssertionHandler::handleExceptionNotThrownAsExpected() +{ + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); +} + +void AssertionHandler::handleUnexpectedExceptionNotThrown() +{ + m_resultCapture.handleUnexpectedExceptionNotThrown(m_assertionInfo, m_reaction); +} + +void AssertionHandler::handleThrowingCallSkipped() +{ + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); +} + +// This is the overload that takes a string and infers the Equals matcher from it +// The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp +void handleExceptionMatchExpr(AssertionHandler &handler, std::string const &str, StringRef const &matcherString) +{ + handleExceptionMatchExpr(handler, Matchers::Equals(str), matcherString); +} + +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch +{ +AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const &_lazyExpression) : lazyExpression(_lazyExpression), + resultType(_resultType) {} + +std::string AssertionResultData::reconstructExpression() const +{ + + if (reconstructedExpression.empty()) + { + if (lazyExpression) + { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; +} + +AssertionResult::AssertionResult(AssertionInfo const &info, AssertionResultData const &data) + : m_info(info), + m_resultData(data) +{ +} + +// 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[0] != 0; +} + +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 +{ + std::string expr; + if (m_info.macroName[0] == 0) + expr = m_info.capturedExpression; + else + { + expr.reserve(m_info.macroName.size() + m_info.capturedExpression.size() + 4); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; +} + +bool AssertionResult::hasExpandedExpression() const +{ + return hasExpression() && getExpandedExpression() != getExpression(); +} + +std::string AssertionResult::getExpandedExpression() const +{ + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; +} + +std::string AssertionResult::getMessage() const +{ + return m_resultData.message; +} +SourceLineInfo AssertionResult::getSourceInfo() const +{ + return m_info.lineInfo; +} + +StringRef AssertionResult::getTestMacroName() const +{ + return m_info.macroName; +} + +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp + +namespace Catch +{ + +auto BenchmarkLooper::getResolution() -> uint64_t +{ + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); +} + +void BenchmarkLooper::reportStart() +{ + getResultCapture().benchmarkStarting({m_name}); +} +auto BenchmarkLooper::needsMoreIterations() -> bool +{ + auto elapsed = m_timer.getElapsedNanoseconds(); + + // Exponentially increasing iterations until we're confident in our timer resolution + if (elapsed < m_resolution) + { + m_iterationsToRun *= 10; + return true; + } + + getResultCapture().benchmarkEnded({{m_name}, m_count, elapsed}); + return false; +} + +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp + +namespace Catch +{ + +using StringMatcher = Matchers::Impl::MatcherBase; + +// This is the general overload that takes a any string matcher +// There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers +// the Equals matcher (so the header does not mention matchers) +void handleExceptionMatchExpr(AssertionHandler &handler, StringMatcher const &matcher, StringRef const &matcherString) +{ + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr expr(exceptionMessage, matcher, matcherString); + handler.handleExpr(expr); +} + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// 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 CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH - 1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 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) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.4 + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include() && __cplusplus >= 201703L +#include +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// This work is licensed under the BSD 2-Clause license. +// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause +// +// This project is hosted at https://github.com/philsquared/textflowcpp + +#include +#include +#include +#include + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch +{ +namespace clara +{ +namespace TextFlow +{ + +inline auto isWhitespace(char c) -> bool +{ + static std::string chars = " \t\n\r"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableBefore(char c) -> bool +{ + static std::string chars = "[({<|"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableAfter(char c) -> bool +{ + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find(c) != std::string::npos; +} + +class Columns; + +class Column +{ + std::vector m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + +public: + class iterator + { + friend Column; + + Column const &m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator(Column const &column, size_t stringIndex) + : m_column(column), + m_stringIndex(stringIndex) + { + } + + auto line() const -> std::string const & { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary(size_t at) const -> bool + { + assert(at > 0); + assert(at <= line().size()); + + return at == line().size() || + (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) || + isBreakableBefore(line()[at]) || + isBreakableAfter(line()[at - 1]); + } + + void calcLength() + { + assert(m_stringIndex < m_column.m_strings.size()); + + m_suffix = false; + auto width = m_column.m_width - indent(); + m_end = m_pos; + while (m_end < line().size() && line()[m_end] != '\n') + ++m_end; + + if (m_end < m_pos + width) + { + m_len = m_end - m_pos; + } + else + { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace(line()[m_pos + len - 1])) + --len; + + if (len > 0) + { + m_len = len; + } + else + { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t + { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string + { + return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); + } + + public: + explicit iterator(Column const &column) : m_column(column) + { + assert(m_column.m_width > m_column.m_indent); + assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); + calcLength(); + if (m_len == 0) + m_stringIndex++; // Empty string + } + + auto operator*() const -> std::string + { + assert(m_stringIndex < m_column.m_strings.size()); + assert(m_pos <= m_end); + if (m_pos + m_column.m_width < m_end) + return addIndentAndSuffix(line().substr(m_pos, m_len)); + else + return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); + } + + auto operator++() -> iterator & + { + m_pos += m_len; + if (m_pos < line().size() && line()[m_pos] == '\n') + m_pos += 1; + else + while (m_pos < line().size() && isWhitespace(line()[m_pos])) + ++m_pos; + + if (m_pos == line().size()) + { + m_pos = 0; + ++m_stringIndex; + } + if (m_stringIndex < m_column.m_strings.size()) + calcLength(); + return *this; + } + auto operator++(int) -> iterator + { + iterator prev(*this); + operator++(); + return prev; + } + + auto operator==(iterator const &other) const -> bool + { + return m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator!=(iterator const &other) const -> bool + { + return !operator==(other); + } + }; + using const_iterator = iterator; + + explicit Column(std::string const &text) { m_strings.push_back(text); } + + auto width(size_t newWidth) -> Column & + { + assert(newWidth > 0); + m_width = newWidth; + return *this; + } + auto indent(size_t newIndent) -> Column & + { + m_indent = newIndent; + return *this; + } + auto initialIndent(size_t newIndent) -> Column & + { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return {*this, m_strings.size()}; } + + inline friend std::ostream &operator<<(std::ostream &os, Column const &col) + { + bool first = true; + for (auto line : col) + { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator+(Column const &other) -> Columns; + + auto toString() const -> std::string + { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +class Spacer : public Column +{ + +public: + explicit Spacer(size_t spaceWidth) : Column("") + { + width(spaceWidth); + } +}; + +class Columns +{ + std::vector m_columns; + +public: + class iterator + { + friend Columns; + struct EndTag + { + }; + + std::vector const &m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator(Columns const &columns, EndTag) + : m_columns(columns.m_columns), + m_activeIterators(0) + { + m_iterators.reserve(m_columns.size()); + + for (auto const &col : m_columns) + m_iterators.push_back(col.end()); + } + + public: + explicit iterator(Columns const &columns) + : m_columns(columns.m_columns), + m_activeIterators(m_columns.size()) + { + m_iterators.reserve(m_columns.size()); + + for (auto const &col : m_columns) + m_iterators.push_back(col.begin()); + } + + auto operator==(iterator const &other) const -> bool + { + return m_iterators == other.m_iterators; + } + auto operator!=(iterator const &other) const -> bool + { + return m_iterators != other.m_iterators; + } + auto operator*() const -> std::string + { + std::string row, padding; + + for (size_t i = 0; i < m_columns.size(); ++i) + { + auto width = m_columns[i].width(); + if (m_iterators[i] != m_columns[i].end()) + { + std::string col = *m_iterators[i]; + row += padding + col; + if (col.size() < width) + padding = std::string(width - col.size(), ' '); + else + padding = ""; + } + else + { + padding += std::string(width, ' '); + } + } + return row; + } + auto operator++() -> iterator & + { + for (size_t i = 0; i < m_columns.size(); ++i) + { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator++(int) -> iterator + { + iterator prev(*this); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return {*this, iterator::EndTag()}; } + + auto operator+=(Column const &col) -> Columns & + { + m_columns.push_back(col); + return *this; + } + auto operator+(Column const &col) -> Columns + { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream &operator<<(std::ostream &os, Columns const &cols) + { + + bool first = true; + for (auto line : cols) + { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string + { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +inline auto Column::operator+(Column const &other) -> Columns +{ + Columns cols; + cols += *this; + cols += other; + return cols; +} +} // namespace TextFlow +} // namespace clara +} // namespace Catch + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include +#include +#include + +#if !defined(CATCH_PLATFORM_WINDOWS) && (defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch +{ +namespace clara +{ +namespace detail +{ + +// Traits for extracting arg and return type of lambdas (for single argument lambdas) +template +struct UnaryLambdaTraits : UnaryLambdaTraits +{ +}; + +template +struct UnaryLambdaTraits +{ + static const bool isValid = false; +}; + +template +struct UnaryLambdaTraits +{ + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type; + using ReturnType = ReturnT; +}; + +class TokenStream; + +// Transport for raw args (copied from main args, or supplied via init list for testing) +class Args +{ + friend TokenStream; + std::string m_exeName; + std::vector m_args; + +public: + Args(int argc, char const *const *argv) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} + + Args(std::initializer_list args) + : m_exeName(*args.begin()), + m_args(args.begin() + 1, args.end()) + { + } + + auto exeName() const -> std::string + { + return m_exeName; + } +}; + +// Wraps a token coming from a token stream. These may not directly correspond to strings as a single string +// may encode an option + its argument if the : or = form is used +enum class TokenType +{ + Option, + Argument +}; +struct Token +{ + TokenType type; + std::string token; +}; + +inline auto isOptPrefix(char c) -> bool +{ + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; +} + +// Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled +class TokenStream +{ + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() + { + m_tokenBuffer.resize(0); + + // Skip any empty strings + while (it != itEnd && it->empty()) + ++it; + + if (it != itEnd) + { + auto const &next = *it; + if (isOptPrefix(next[0])) + { + auto delimiterPos = next.find_first_of(" :="); + if (delimiterPos != std::string::npos) + { + m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)}); + m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)}); + } + else + { + if (next[1] != '-' && next.size() > 2) + { + std::string opt = "- "; + for (size_t i = 1; i < next.size(); ++i) + { + opt[1] = next[i]; + m_tokenBuffer.push_back({TokenType::Option, opt}); + } + } + else + { + m_tokenBuffer.push_back({TokenType::Option, next}); + } + } + } + else + { + m_tokenBuffer.push_back({TokenType::Argument, next}); + } + } + } + +public: + explicit TokenStream(Args const &args) : TokenStream(args.m_args.begin(), args.m_args.end()) {} + + TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) + { + loadBuffer(); + } + + explicit operator bool() const + { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token + { + assert(!m_tokenBuffer.empty()); + return m_tokenBuffer.front(); + } + + auto operator-> () const -> Token const * + { + assert(!m_tokenBuffer.empty()); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & + { + if (m_tokenBuffer.size() >= 2) + { + m_tokenBuffer.erase(m_tokenBuffer.begin()); + } + else + { + if (it != itEnd) + ++it; + loadBuffer(); + } + return *this; + } +}; + +class ResultBase +{ +public: + enum Type + { + Ok, + LogicError, + RuntimeError + }; + +protected: + ResultBase(Type type) : m_type(type) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; +}; + +template +class ResultValueBase : public ResultBase +{ +public: + auto value() const -> T const & + { + enforceOk(); + return m_value; + } + +protected: + ResultValueBase(Type type) : ResultBase(type) {} + + ResultValueBase(ResultValueBase const &other) : ResultBase(other) + { + if (m_type == ResultBase::Ok) + new (&m_value) T(other.m_value); + } + + ResultValueBase(Type, T const &value) : ResultBase(Ok) + { + new (&m_value) T(value); + } + + auto operator=(ResultValueBase const &other) -> ResultValueBase & + { + if (m_type == ResultBase::Ok) + m_value.~T(); + ResultBase::operator=(other); + if (m_type == ResultBase::Ok) + new (&m_value) T(other.m_value); + return *this; + } + + ~ResultValueBase() override + { + if (m_type == Ok) + m_value.~T(); + } + + union { + T m_value; + }; +}; + +template <> +class ResultValueBase : public ResultBase +{ +protected: + using ResultBase::ResultBase; +}; + +template +class BasicResult : public ResultValueBase +{ +public: + template + explicit BasicResult(BasicResult const &other) + : ResultValueBase(other.type()), + m_errorMessage(other.errorMessage()) + { + assert(type() != ResultBase::Ok); + } + + template + static auto ok(U const &value) -> BasicResult { return {ResultBase::Ok, value}; } + static auto ok() -> BasicResult { return {ResultBase::Ok}; } + static auto logicError(std::string const &message) -> BasicResult { return {ResultBase::LogicError, message}; } + static auto runtimeError(std::string const &message) -> BasicResult { return {ResultBase::RuntimeError, message}; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + +protected: + void enforceOk() const override + { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert(m_type != ResultBase::LogicError); + assert(m_type != ResultBase::RuntimeError); + if (m_type != ResultBase::Ok) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult(ResultBase::Type type, std::string const &message) + : ResultValueBase(type), + m_errorMessage(message) + { + assert(m_type != ResultBase::Ok); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; +}; + +enum class ParseResultType +{ + Matched, + NoMatch, + ShortCircuitAll, + ShortCircuitSame +}; + +class ParseState +{ +public: + ParseState(ParseResultType type, TokenStream const &remainingTokens) + : m_type(type), + m_remainingTokens(remainingTokens) + { + } + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + +private: + ParseResultType m_type; + TokenStream m_remainingTokens; +}; + +using Result = BasicResult; +using ParserResult = BasicResult; +using InternalParseResult = BasicResult; + +struct HelpColumns +{ + std::string left; + std::string right; +}; + +template +inline auto convertInto(std::string const &source, T &target) -> ParserResult +{ + std::stringstream ss; + ss << source; + ss >> target; + if (ss.fail()) + return ParserResult::runtimeError("Unable to convert '" + source + "' to destination type"); + else + return ParserResult::ok(ParseResultType::Matched); +} +inline auto convertInto(std::string const &source, std::string &target) -> ParserResult +{ + target = source; + return ParserResult::ok(ParseResultType::Matched); +} +inline auto convertInto(std::string const &source, bool &target) -> ParserResult +{ + std::string srcLC = source; + std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), [](char c) { return static_cast(::tolower(c)); }); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError("Expected a boolean value but did not recognise: '" + source + "'"); + return ParserResult::ok(ParseResultType::Matched); +} +#ifdef CLARA_CONFIG_OPTIONAL_TYPE +template +inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE &target) -> ParserResult +{ + T temp; + auto result = convertInto(source, temp); + if (result) + target = std::move(temp); + return result; +} +#endif // CLARA_CONFIG_OPTIONAL_TYPE + +struct NonCopyable +{ + NonCopyable() = default; + NonCopyable(NonCopyable const &) = delete; + NonCopyable(NonCopyable &&) = delete; + NonCopyable &operator=(NonCopyable const &) = delete; + NonCopyable &operator=(NonCopyable &&) = delete; +}; + +struct BoundRef : NonCopyable +{ + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } +}; +struct BoundValueRefBase : BoundRef +{ + virtual auto setValue(std::string const &arg) -> ParserResult = 0; +}; +struct BoundFlagRefBase : BoundRef +{ + virtual auto setFlag(bool flag) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } +}; + +template +struct BoundValueRef : BoundValueRefBase +{ + T &m_ref; + + explicit BoundValueRef(T &ref) : m_ref(ref) {} + + auto setValue(std::string const &arg) -> ParserResult override + { + return convertInto(arg, m_ref); + } +}; + +template +struct BoundValueRef> : BoundValueRefBase +{ + std::vector &m_ref; + + explicit BoundValueRef(std::vector &ref) : m_ref(ref) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue(std::string const &arg) -> ParserResult override + { + T temp; + auto result = convertInto(arg, temp); + if (result) + m_ref.push_back(temp); + return result; + } +}; + +struct BoundFlagRef : BoundFlagRefBase +{ + bool &m_ref; + + explicit BoundFlagRef(bool &ref) : m_ref(ref) {} + + auto setFlag(bool flag) -> ParserResult override + { + m_ref = flag; + return ParserResult::ok(ParseResultType::Matched); + } +}; + +template +struct LambdaInvoker +{ + static_assert(std::is_same::value, "Lambda must return void or clara::ParserResult"); + + template + static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult + { + return lambda(arg); + } +}; + +template <> +struct LambdaInvoker +{ + template + static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult + { + lambda(arg); + return ParserResult::ok(ParseResultType::Matched); + } +}; + +template +inline auto invokeLambda(L const &lambda, std::string const &arg) -> ParserResult +{ + ArgType temp{}; + auto result = convertInto(arg, temp); + return !result + ? result + : LambdaInvoker::ReturnType>::invoke(lambda, temp); +} + +template +struct BoundLambda : BoundValueRefBase +{ + L m_lambda; + + static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); + explicit BoundLambda(L const &lambda) : m_lambda(lambda) {} + + auto setValue(std::string const &arg) -> ParserResult override + { + return invokeLambda::ArgType>(m_lambda, arg); + } +}; + +template +struct BoundFlagLambda : BoundFlagRefBase +{ + L m_lambda; + + static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); + static_assert(std::is_same::ArgType, bool>::value, "flags must be boolean"); + + explicit BoundFlagLambda(L const &lambda) : m_lambda(lambda) {} + + auto setFlag(bool flag) -> ParserResult override + { + return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); + } +}; + +enum class Optionality +{ + Optional, + Required +}; + +struct Parser; + +class ParserBase +{ +public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse(std::string const &exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse(Args const &args) const -> InternalParseResult + { + return parse(args.exeName(), TokenStream(args)); + } +}; + +template +class ComposableParserImpl : public ParserBase +{ +public: + template + auto operator|(T const &other) const -> Parser; + + template + auto operator+(T const &other) const -> Parser; +}; + +// Common code and state for Args and Opts +template +class ParserRefImpl : public ComposableParserImpl +{ +protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl(std::shared_ptr const &ref) : m_ref(ref) {} + +public: + template + ParserRefImpl(T &ref, std::string const &hint) + : m_ref(std::make_shared>(ref)), + m_hint(hint) + { + } + + template + ParserRefImpl(LambdaT const &ref, std::string const &hint) + : m_ref(std::make_shared>(ref)), + m_hint(hint) + { + } + + auto operator()(std::string const &description) -> DerivedT & + { + m_description = description; + return static_cast(*this); + } + + auto optional() -> DerivedT & + { + m_optionality = Optionality::Optional; + return static_cast(*this); + }; + + auto required() -> DerivedT & + { + m_optionality = Optionality::Required; + return static_cast(*this); + }; + + auto isOptional() const -> bool + { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override + { + if (m_ref->isContainer()) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } +}; + +class ExeName : public ComposableParserImpl +{ + std::shared_ptr m_name; + std::shared_ptr m_ref; + + template + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr + { + return std::make_shared>(lambda); + } + +public: + ExeName() : m_name(std::make_shared("")) {} + + explicit ExeName(std::string &ref) : ExeName() + { + m_ref = std::make_shared>(ref); + } + + template + explicit ExeName(LambdaT const &lambda) : ExeName() + { + m_ref = std::make_shared>(lambda); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override + { + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); + } + + auto name() const -> std::string { return *m_name; } + auto set(std::string const &newName) -> ParserResult + { + + auto lastSlash = newName.find_last_of("\\/"); + auto filename = (lastSlash == std::string::npos) + ? newName + : newName.substr(lastSlash + 1); + + *m_name = filename; + if (m_ref) + return m_ref->setValue(filename); + else + return ParserResult::ok(ParseResultType::Matched); + } +}; + +class Arg : public ParserRefImpl +{ +public: + using ParserRefImpl::ParserRefImpl; + + auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override + { + auto validationResult = validate(); + if (!validationResult) + return InternalParseResult(validationResult); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if (token.type != TokenType::Argument) + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); + + assert(!m_ref->isFlag()); + auto valueRef = static_cast(m_ref.get()); + + auto result = valueRef->setValue(remainingTokens->token); + if (!result) + return InternalParseResult(result); + else + return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); + } +}; + +inline auto normaliseOpt(std::string const &optName) -> std::string +{ +#ifdef CATCH_PLATFORM_WINDOWS + if (optName[0] == '/') + return "-" + optName.substr(1); + else +#endif + return optName; +} + +class Opt : public ParserRefImpl +{ +protected: + std::vector m_optNames; + +public: + template + explicit Opt(LambdaT const &ref) : ParserRefImpl(std::make_shared>(ref)) {} + + explicit Opt(bool &ref) : ParserRefImpl(std::make_shared(ref)) {} + + template + Opt(LambdaT const &ref, std::string const &hint) : ParserRefImpl(ref, hint) {} + + template + Opt(T &ref, std::string const &hint) : ParserRefImpl(ref, hint) {} + + auto operator[](std::string const &optName) -> Opt & + { + m_optNames.push_back(optName); + return *this; + } + + auto getHelpColumns() const -> std::vector + { + std::ostringstream oss; + bool first = true; + for (auto const &opt : m_optNames) + { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if (!m_hint.empty()) + oss << " <" << m_hint << ">"; + return {{oss.str(), m_description}}; + } + + auto isMatch(std::string const &optToken) const -> bool + { + auto normalisedToken = normaliseOpt(optToken); + for (auto const &name : m_optNames) + { + if (normaliseOpt(name) == normalisedToken) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override + { + auto validationResult = validate(); + if (!validationResult) + return InternalParseResult(validationResult); + + auto remainingTokens = tokens; + if (remainingTokens && remainingTokens->type == TokenType::Option) + { + auto const &token = *remainingTokens; + if (isMatch(token.token)) + { + if (m_ref->isFlag()) + { + auto flagRef = static_cast(m_ref.get()); + auto result = flagRef->setFlag(true); + if (!result) + return InternalParseResult(result); + if (result.value() == ParseResultType::ShortCircuitAll) + return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); + } + else + { + auto valueRef = static_cast(m_ref.get()); + ++remainingTokens; + if (!remainingTokens) + return InternalParseResult::runtimeError("Expected argument following " + token.token); + auto const &argToken = *remainingTokens; + if (argToken.type != TokenType::Argument) + return InternalParseResult::runtimeError("Expected argument following " + token.token); + auto result = valueRef->setValue(argToken.token); + if (!result) + return InternalParseResult(result); + if (result.value() == ParseResultType::ShortCircuitAll) + return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); + } + return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); + } + } + return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); + } + + auto validate() const -> Result override + { + if (m_optNames.empty()) + return Result::logicError("No options supplied to Opt"); + for (auto const &name : m_optNames) + { + if (name.empty()) + return Result::logicError("Option name cannot be empty"); +#ifdef CATCH_PLATFORM_WINDOWS + if (name[0] != '-' && name[0] != '/') + return Result::logicError("Option name must begin with '-' or '/'"); +#else + if (name[0] != '-') + return Result::logicError("Option name must begin with '-'"); +#endif + } + return ParserRefImpl::validate(); + } +}; + +struct Help : Opt +{ + Help(bool &showHelpFlag) + : Opt([&](bool flag) { + showHelpFlag = flag; + return ParserResult::ok(ParseResultType::ShortCircuitAll); + }) + { + static_cast (*this)("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } +}; + +struct Parser : ParserBase +{ + + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator|=(ExeName const &exeName) -> Parser & + { + m_exeName = exeName; + return *this; + } + + auto operator|=(Arg const &arg) -> Parser & + { + m_args.push_back(arg); + return *this; + } + + auto operator|=(Opt const &opt) -> Parser & + { + m_options.push_back(opt); + return *this; + } + + auto operator|=(Parser const &other) -> Parser & + { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator|(T const &other) const -> Parser + { + return Parser(*this) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template + auto operator+=(T const &other) -> Parser & { return operator|=(other); } + template + auto operator+(T const &other) const -> Parser { return operator|(other); } + + auto getHelpColumns() const -> std::vector + { + std::vector cols; + for (auto const &o : m_options) + { + auto childCols = o.getHelpColumns(); + cols.insert(cols.end(), childCols.begin(), childCols.end()); + } + return cols; + } + + void writeToStream(std::ostream &os) const + { + if (!m_exeName.name().empty()) + { + os << "usage:\n" + << " " << m_exeName.name() << " "; + bool required = true, first = true; + for (auto const &arg : m_args) + { + if (first) + first = false; + else + os << " "; + if (arg.isOptional() && required) + { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if (arg.cardinality() == 0) + os << " ... "; + } + if (!required) + os << "]"; + if (!m_options.empty()) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for (auto const &cols : rows) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth / 2); + + for (auto const &cols : rows) + { + auto row = + TextFlow::Column(cols.left).width(optWidth).indent(2) + + TextFlow::Spacer(4) + + TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth); + os << row << std::endl; + } + } + + friend auto operator<<(std::ostream &os, Parser const &parser) -> std::ostream & + { + parser.writeToStream(os); + return os; + } + + auto validate() const -> Result override + { + for (auto const &opt : m_options) + { + auto result = opt.validate(); + if (!result) + return result; + } + for (auto const &arg : m_args) + { + auto result = arg.validate(); + if (!result) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse(std::string const &exeName, TokenStream const &tokens) const -> InternalParseResult override + { + + struct ParserInfo + { + ParserBase const *parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert(totalParsers < 512); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) + parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) + parseInfos[i++].parser = &arg; + } + + m_exeName.set(exeName); + + auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); + while (result.value().remainingTokens()) + { + bool tokenParsed = false; + + for (size_t i = 0; i < totalParsers; ++i) + { + auto &parseInfo = parseInfos[i]; + if (parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality()) + { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) + { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if (result.value().type() == ParseResultType::ShortCircuitAll) + return result; + if (!tokenParsed) + return InternalParseResult::runtimeError("Unrecognised token: " + result.value().remainingTokens()->token); + } + // !TBD Check missing required options + return result; + } +}; + +template +template +auto ComposableParserImpl::operator|(T const &other) const -> Parser +{ + return Parser() | static_cast(*this) | other; +} +} // namespace detail + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +} // namespace clara +} // namespace Catch + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch +{ + +clara::Parser makeCommandLineParser(ConfigData &config); + +} // end namespace Catch + +// end catch_commandline.h +#include +#include + +namespace Catch +{ + +clara::Parser makeCommandLineParser(ConfigData &config) +{ + + using namespace clara; + + auto const setWarning = [&](std::string const &warning) { + auto warningSet = [&]() { + if (warning == "NoAssertions") + return WarnAbout::NoAssertions; + + if (warning == "NoTests") + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError("Unrecognised warning: '" + warning + "'"); + config.warnings = static_cast(config.warnings | warningSet); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const loadTestNamesFromFile = [&](std::string const &filename) { + std::ifstream f(filename.c_str()); + if (!f.is_open()) + return ParserResult::runtimeError("Unable to load input file: '" + filename + "'"); + + std::string line; + while (std::getline(f, line)) + { + line = trim(line); + if (!line.empty() && !startsWith(line, '#')) + { + if (!startsWith(line, '"')) + line = '"' + line + '"'; + config.testsOrTags.push_back(line + ','); + } + } + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setTestOrder = [&](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 + return clara::ParserResult::runtimeError("Unrecognised ordering: '" + order + "'"); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setRngSeed = [&](std::string const &seed) { + if (seed != "time") + return clara::detail::convertInto(seed, config.rngSeed); + config.rngSeed = static_cast(std::time(nullptr)); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setColourUsage = [&](std::string const &useColour) { + auto mode = toLower(useColour); + + if (mode == "yes") + config.useColour = UseColour::Yes; + else if (mode == "no") + config.useColour = UseColour::No; + else if (mode == "auto") + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError("colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised"); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setWaitForKeypress = [&](std::string const &keypress) { + auto keypressLc = toLower(keypress); + if (keypressLc == "start") + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if (keypressLc == "exit") + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if (keypressLc == "both") + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError("keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised"); + return ParserResult::ok(ParseResultType::Matched); + }; + auto const setVerbosity = [&](std::string const &verbosity) { + auto lcVerbosity = toLower(verbosity); + if (lcVerbosity == "quiet") + config.verbosity = Verbosity::Quiet; + else if (lcVerbosity == "normal") + config.verbosity = Verbosity::Normal; + else if (lcVerbosity == "high") + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError("Unrecognised verbosity, '" + verbosity + "'"); + return ParserResult::ok(ParseResultType::Matched); + }; + + auto cli = ExeName(config.processName) | Help(config.showHelp) | Opt(config.listTests)["-l"]["--list-tests"]("list all/matching test cases") | Opt(config.listTags)["-t"]["--list-tags"]("list all/matching tags") | Opt(config.showSuccessfulTests)["-s"]["--success"]("include successful tests in output") | Opt(config.shouldDebugBreak)["-b"]["--break"]("break into debugger on failure") | Opt(config.noThrow)["-e"]["--nothrow"]("skip exception tests") | Opt(config.showInvisibles)["-i"]["--invisibles"]("show invisibles (tabs, newlines)") | Opt(config.outputFilename, "filename")["-o"]["--out"]("output filename") | Opt(config.reporterName, "name")["-r"]["--reporter"]("reporter to use (defaults to console)") | Opt(config.name, "name")["-n"]["--name"]("suite name") | Opt([&](bool) { config.abortAfter = 1; })["-a"]["--abort"]("abort at first failure") | Opt([&](int x) { config.abortAfter = x; }, "no. failures")["-x"]["--abortx"]("abort after x failures") | Opt(setWarning, "warning name")["-w"]["--warn"]("enable warnings") | Opt([&](bool flag) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no")["-d"]["--durations"]("show test durations") | Opt(loadTestNamesFromFile, "filename")["-f"]["--input-file"]("load test names to run from a file") | Opt(config.filenamesAsTags)["-#"]["--filenames-as-tags"]("adds a tag for the filename") | Opt(config.sectionsToRun, "section name")["-c"]["--section"]("specify section to run") | Opt(setVerbosity, "quiet|normal|high")["-v"]["--verbosity"]("set output verbosity") | Opt(config.listTestNamesOnly)["--list-test-names-only"]("list all/matching test cases names only") | Opt(config.listReporters)["--list-reporters"]("list all reporters") | Opt(setTestOrder, "decl|lex|rand")["--order"]("test case order (defaults to decl)") | Opt(setRngSeed, "'time'|number")["--rng-seed"]("set a specific seed for random numbers") | Opt(setColourUsage, "yes|no")["--use-colour"]("should output be colourised") | Opt(config.libIdentify)["--libidentify"]("report name and version according to libidentify standard") | Opt(setWaitForKeypress, "start|exit|both")["--wait-for-keypress"]("waits for a keypress before exiting") | Opt(config.benchmarkResolutionMultiple, "multiplier")["--benchmark-resolution-multiple"]("multiple of clock resolution to run benchmarks") + + | Arg(config.testsOrTags, "test name|pattern|tags")("which test or tests to use"); + + return cli; +} + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp + +#include +#include + +namespace Catch +{ + +bool SourceLineInfo::empty() const noexcept +{ + return file[0] == '\0'; +} +bool SourceLineInfo::operator==(SourceLineInfo const &other) const noexcept +{ + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); +} +bool SourceLineInfo::operator<(SourceLineInfo const &other) const noexcept +{ + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || (line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); +} + +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; +} + +std::string StreamEndStop::operator+() const +{ + return std::string(); +} + +NonCopyable::NonCopyable() = default; +NonCopyable::~NonCopyable() = default; + +} // namespace Catch +// end catch_common.cpp +// start catch_config.cpp + +namespace Catch +{ + +Config::Config(ConfigData const &data) + : m_data(data), + m_stream(openStream()) +{ + TestSpecParser parser(ITagAliasRegistry::get()); + if (data.testsOrTags.empty()) + { + parser.parse("~[.]"); // All not hidden tests + } + else + { + m_hasTestFilters = true; + for (auto const &testOrTags : data.testsOrTags) + parser.parse(testOrTags); + } + m_testSpec = parser.testSpec(); +} + +std::string const &Config::getFilename() const +{ + return m_data.outputFilename; +} + +bool Config::listTests() const { return m_data.listTests; } +bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } +bool Config::listTags() const { return m_data.listTags; } +bool Config::listReporters() const { return m_data.listReporters; } + +std::string Config::getProcessName() const { return m_data.processName; } +std::string const &Config::getReporterName() const { return m_data.reporterName; } + +std::vector const &Config::getTestsOrTags() const { return m_data.testsOrTags; } +std::vector const &Config::getSectionsToRun() const { return m_data.sectionsToRun; } + +TestSpec const &Config::testSpec() const { return m_testSpec; } +bool Config::hasTestFilters() const { return m_hasTestFilters; } + +bool Config::showHelp() const { return m_data.showHelp; } + +// IConfig interface +bool Config::allowThrows() const { return !m_data.noThrow; } +std::ostream &Config::stream() const { return m_stream->stream(); } +std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } +bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } +bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } +bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } +ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } +RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } +unsigned int Config::rngSeed() const { return m_data.rngSeed; } +int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } +UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } +bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } +int Config::abortAfter() const { return m_data.abortAfter; } +bool Config::showInvisibles() const { return m_data.showInvisibles; } +Verbosity Config::verbosity() const { return m_data.verbosity; } + +IStream const *Config::openStream() +{ + return Catch::makeStream(m_data.outputFilename); +} + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch +{ + +class ErrnoGuard +{ +public: + ErrnoGuard(); + ~ErrnoGuard(); + +private: + int m_oldErrno; +}; + +} // namespace Catch + +// end catch_errno_guard.h +#include + +namespace Catch +{ +namespace +{ + +struct IColourImpl +{ + virtual ~IColourImpl() = default; + virtual void use(Colour::Code _colourCode) = 0; +}; + +struct NoColourImpl : IColourImpl +{ + void use(Colour::Code) {} + + static IColourImpl *instance() + { + static NoColourImpl s_instance; + return &s_instance; + } +}; + +} // 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) ///////////////////////////////////////// + +namespace Catch +{ +namespace +{ + +class Win32ColourImpl : public IColourImpl +{ +public: + Win32ColourImpl() : stdoutHandle(GetStdHandle(STD_OUTPUT_HANDLE)) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); + 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) override + { + switch (_colourCode) + { + 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); + 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::BrightYellow: + return setTextAttribute(FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); + + case Colour::Bright: + CATCH_INTERNAL_ERROR("not a colour"); + + default: + CATCH_ERROR("Unknown colour requested"); + } + } + +private: + void setTextAttribute(WORD _textAttribute) + { + SetConsoleTextAttribute(stdoutHandle, _textAttribute | originalBackgroundAttributes); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; +}; + +IColourImpl *platformColourInstance() +{ + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if (colourMode == UseColour::Auto) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); +} + +} // 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) override + { + 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::BrightYellow: + return setColour("[1;33m"); + + case Colour::Bright: + CATCH_INTERNAL_ERROR("not a colour"); + default: + CATCH_INTERNAL_ERROR("Unknown colour requested"); + } + } + static IColourImpl *instance() + { + static PosixColourImpl s_instance; + return &s_instance; + } + +private: + void setColour(const char *_escapeCode) + { + Catch::cout() << '\033' << _escapeCode; + } +}; + +bool useColourOnPlatform() +{ + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; +} +IColourImpl *platformColourInstance() +{ + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if (colourMode == UseColour::Auto) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); +} + +} // 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) { use(_colourCode); } +Colour::Colour(Colour &&rhs) noexcept +{ + m_moved = rhs.m_moved; + rhs.m_moved = true; +} +Colour &Colour::operator=(Colour &&rhs) noexcept +{ + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; +} + +Colour::~Colour() +{ + if (!m_moved) + use(None); +} + +void Colour::use(Code _colourCode) +{ + static IColourImpl *impl = platformColourInstance(); + impl->use(_colourCode); +} + +std::ostream &operator<<(std::ostream &os, Colour const &) +{ + return os; +} + +} // end namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp + +namespace Catch +{ + +class Context : public IMutableContext, NonCopyable +{ + +public: // IContext + virtual IResultCapture *getResultCapture() override + { + return m_resultCapture; + } + virtual IRunner *getRunner() override + { + return m_runner; + } + + virtual IConfigPtr const &getConfig() const override + { + return m_config; + } + + virtual ~Context() override; + +public: // IMutableContext + virtual void setResultCapture(IResultCapture *resultCapture) override + { + m_resultCapture = resultCapture; + } + virtual void setRunner(IRunner *runner) override + { + m_runner = runner; + } + virtual void setConfig(IConfigPtr const &config) override + { + m_config = config; + } + + friend IMutableContext &getCurrentMutableContext(); + +private: + IConfigPtr m_config; + IRunner *m_runner = nullptr; + IResultCapture *m_resultCapture = nullptr; +}; + +IMutableContext *IMutableContext::currentContext = nullptr; + +void IMutableContext::createContext() +{ + currentContext = new Context(); +} + +void cleanUpContext() +{ + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; +} +IContext::~IContext() = default; +IMutableContext::~IMutableContext() = default; +Context::~Context() = default; +} // namespace Catch +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include + +namespace Catch +{ +void writeToDebugConsole(std::string const &text); +} + +// end catch_debug_console.h +#ifdef CATCH_PLATFORM_WINDOWS + +namespace Catch +{ +void writeToDebugConsole(std::string const &text) +{ + ::OutputDebugStringA(text.c_str()); +} +} // namespace Catch + +#else + +namespace Catch +{ +void writeToDebugConsole(std::string const &text) +{ + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; +} +} // namespace Catch + +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp + +#ifdef CATCH_PLATFORM_MAC + +#include +#include +#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; + std::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, nullptr, 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(CATCH_PLATFORM_LINUX) +#include +#include + +namespace Catch +{ +// The standard POSIX way of detecting a debugger is to attempt to +// ptrace() the process, but this needs to be done from a child and not +// this process itself to still allow attaching to this process later +// if wanted, so is rather heavy. Under Linux we have the PID of the +// "debugger" (which doesn't need to be gdb, of course, it could also +// be strace, for example) in /proc/$PID/status, so just get it from +// there instead. +bool isDebuggerActive() +{ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for (std::string line; std::getline(in, line);) + { + static const int PREFIX_LEN = 11; + if (line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) + { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; +} +} // namespace Catch +#elif defined(_MSC_VER) +extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); +namespace Catch +{ +bool isDebuggerActive() +{ + return IsDebuggerPresent() != 0; +} +} // namespace Catch +#elif defined(__MINGW32__) +extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); +namespace Catch +{ +bool isDebuggerActive() +{ + return IsDebuggerPresent() != 0; +} +} // namespace Catch +#else +namespace Catch +{ +bool isDebuggerActive() { return false; } +} // namespace Catch +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp + +namespace Catch +{ + +ITransientExpression::~ITransientExpression() = default; + +void formatReconstructedExpression(std::ostream &os, std::string const &lhs, StringRef op, std::string const &rhs) +{ + if (lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" + << op << "\n" + << rhs; +} +} // namespace Catch +// end catch_decomposer.cpp +// start catch_enforce.cpp + +namespace Catch +{ +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) +[[noreturn]] void throw_exception(std::exception const &e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); +} +#endif +} // namespace Catch +// end catch_enforce.cpp +// start catch_errno_guard.cpp + +#include + +namespace Catch +{ +ErrnoGuard::ErrnoGuard() : m_oldErrno(errno) {} +ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} // namespace Catch +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp + +// start catch_exception_translator_registry.h + +#include +#include +#include + +namespace Catch +{ + +class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry +{ +public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator(const IExceptionTranslator *translator); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + +private: + std::vector> m_translators; +}; +} // namespace Catch + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch +{ + +ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() +{ +} + +void ExceptionTranslatorRegistry::registerTranslator(const IExceptionTranslator *translator) +{ + m_translators.push_back(std::unique_ptr(translator)); +} + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +std::string ExceptionTranslatorRegistry::translateActiveException() const +{ + try + { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try + { + return tryTranslators(); + } + @catch (NSException *exception) + { + return Catch::Detail::stringify([exception description]); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) + { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif + } + catch (TestFailureException &) + { + std::rethrow_exception(std::current_exception()); + } + catch (std::exception &ex) + { + return ex.what(); + } + catch (std::string &msg) + { + return msg; + } + catch (const char *msg) + { + return msg; + } + catch (...) + { + return "Unknown exception"; + } +} + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv +std::string ExceptionTranslatorRegistry::translateActiveException() const +{ + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); +} +#endif + +std::string ExceptionTranslatorRegistry::tryTranslators() const +{ + if (m_translators.empty()) + std::rethrow_exception(std::current_exception()); + else + return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end()); +} +} // namespace Catch +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#if defined(CATCH_CONFIG_WINDOWS_SEH) || defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace +{ +// Report the error condition +void reportFatal(char const *const message) +{ + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition(message); +} +} // namespace + +#endif // signals/SEH handling + +#if defined(CATCH_CONFIG_WINDOWS_SEH) + +namespace Catch +{ +struct SignalDefs +{ + DWORD id; + const char *name; +}; + +// There is no 1-1 mapping between signals and windows exceptions. +// Windows can easily distinguish between SO and SigSegV, +// but SigInt, SigTerm, etc are handled differently. +static SignalDefs signalDefs[] = { + {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, + {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, + {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, + {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, +}; + +LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) +{ + for (auto const &def : signalDefs) + { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) + { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; +} + +FatalConditionHandler::FatalConditionHandler() +{ + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); +} + +void FatalConditionHandler::reset() +{ + if (isSet) + { + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } +} + +FatalConditionHandler::~FatalConditionHandler() +{ + reset(); +} + +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +} // namespace Catch + +#elif defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch +{ + +struct SignalDefs +{ + int id; + const char *name; +}; + +// 32kb for the alternate stack seems to be sufficient. However, this value +// is experimentally determined, so that's not guaranteed. +constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + +static 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"}}; + +void FatalConditionHandler::handleSignal(int sig) +{ + char const *name = ""; + for (auto const &def : signalDefs) + { + if (sig == def.id) + { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); +} + +FatalConditionHandler::FatalConditionHandler() +{ + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sigStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) + { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } +} + +FatalConditionHandler::~FatalConditionHandler() +{ + reset(); +} + +void FatalConditionHandler::reset() +{ + if (isSet) + { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) + { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } +} + +bool FatalConditionHandler::isSet = false; +struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)] = {}; +stack_t FatalConditionHandler::oldSigStack = {}; +char FatalConditionHandler::altStackMem[sigStackSize] = {}; + +} // namespace Catch + +#else + +namespace Catch +{ +void FatalConditionHandler::reset() {} +} // namespace Catch + +#endif // signals/SEH handling + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +// end catch_fatal_condition.cpp +// start catch_generators.cpp + +// start catch_random_number_generator.h + +#include +#include + +namespace Catch +{ + +struct IConfig; + +std::mt19937 &rng(); +void seedRng(IConfig const &config); +unsigned int rngSeed(); + +} // namespace Catch + +// end catch_random_number_generator.h +#include +#include + +namespace Catch +{ + +IGeneratorTracker::~IGeneratorTracker() {} + +namespace Generators +{ + +GeneratorBase::~GeneratorBase() {} + +std::vector randomiseIndices(size_t selectionSize, size_t sourceSize) +{ + + assert(selectionSize <= sourceSize); + std::vector indices; + indices.reserve(selectionSize); + std::uniform_int_distribution uid(0, sourceSize - 1); + + std::set seen; + // !TBD: improve this algorithm + while (indices.size() < selectionSize) + { + auto index = uid(rng()); + if (seen.insert(index).second) + indices.push_back(index); + } + return indices; +} + +auto acquireGeneratorTracker(SourceLineInfo const &lineInfo) -> IGeneratorTracker & +{ + return getResultCapture().acquireGeneratorTracker(lineInfo); +} + +template <> +auto all() -> Generator +{ + return range(std::numeric_limits::min(), std::numeric_limits::max()); +} + +} // namespace Generators +} // namespace Catch +// end catch_generators.cpp +// start catch_interfaces_capture.cpp + +namespace Catch +{ +IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch +{ +IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch +{ +IExceptionTranslator::~IExceptionTranslator() = default; +IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} // namespace Catch +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch +{ +IRegistryHub::~IRegistryHub() = default; +IMutableRegistryHub::~IMutableRegistryHub() = default; +} // namespace Catch +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch +{ + +class ListeningReporter : public IStreamingReporter +{ + using Reporters = std::vector; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; + +public: + ListeningReporter(); + + void addListener(IStreamingReporterPtr &&listener); + void addReporter(IStreamingReporterPtr &&reporter); + +public: // IStreamingReporter + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const &spec) override; + + static std::set getSupportedVerbosities(); + + void benchmarkStarting(BenchmarkInfo const &benchmarkInfo) override; + void benchmarkEnded(BenchmarkStats const &benchmarkStats) override; + + void testRunStarting(TestRunInfo const &testRunInfo) override; + void testGroupStarting(GroupInfo const &groupInfo) override; + void testCaseStarting(TestCaseInfo const &testInfo) override; + void sectionStarting(SectionInfo const §ionInfo) override; + void assertionStarting(AssertionInfo const &assertionInfo) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded(AssertionStats const &assertionStats) override; + void sectionEnded(SectionStats const §ionStats) override; + void testCaseEnded(TestCaseStats const &testCaseStats) override; + void testGroupEnded(TestGroupStats const &testGroupStats) override; + void testRunEnded(TestRunStats const &testRunStats) override; + + void skipTest(TestCaseInfo const &testInfo) override; + bool isMulti() const override; +}; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch +{ + +ReporterConfig::ReporterConfig(IConfigPtr const &_fullConfig) + : m_stream(&_fullConfig->stream()), m_fullConfig(_fullConfig) {} + +ReporterConfig::ReporterConfig(IConfigPtr const &_fullConfig, std::ostream &_stream) + : m_stream(&_stream), m_fullConfig(_fullConfig) {} + +std::ostream &ReporterConfig::stream() const { return *m_stream; } +IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + +TestRunInfo::TestRunInfo(std::string const &_name) : name(_name) {} + +GroupInfo::GroupInfo(std::string const &_name, + std::size_t _groupIndex, + std::size_t _groupsCount) + : name(_name), + groupIndex(_groupIndex), + groupsCounts(_groupsCount) +{ +} + +AssertionStats::AssertionStats(AssertionResult const &_assertionResult, + std::vector const &_infoMessages, + Totals const &_totals) + : assertionResult(_assertionResult), + infoMessages(_infoMessages), + totals(_totals) +{ + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + 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); + } +} + +AssertionStats::~AssertionStats() = default; + +SectionStats::SectionStats(SectionInfo const &_sectionInfo, + Counts const &_assertions, + double _durationInSeconds, + bool _missingAssertions) + : sectionInfo(_sectionInfo), + assertions(_assertions), + durationInSeconds(_durationInSeconds), + missingAssertions(_missingAssertions) +{ +} + +SectionStats::~SectionStats() = default; + +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) +{ +} + +TestCaseStats::~TestCaseStats() = default; + +TestGroupStats::TestGroupStats(GroupInfo const &_groupInfo, + Totals const &_totals, + bool _aborting) + : groupInfo(_groupInfo), + totals(_totals), + aborting(_aborting) +{ +} + +TestGroupStats::TestGroupStats(GroupInfo const &_groupInfo) + : groupInfo(_groupInfo), + aborting(false) +{ +} + +TestGroupStats::~TestGroupStats() = default; + +TestRunStats::TestRunStats(TestRunInfo const &_runInfo, + Totals const &_totals, + bool _aborting) + : runInfo(_runInfo), + totals(_totals), + aborting(_aborting) +{ +} + +TestRunStats::~TestRunStats() = default; + +void IStreamingReporter::fatalErrorEncountered(StringRef) {} +bool IStreamingReporter::isMulti() const { return false; } + +IReporterFactory::~IReporterFactory() = default; +IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch +{ +IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch +{ +ITestInvoker::~ITestInvoker() = default; +ITestCaseRegistry::~ITestCaseRegistry() = default; +} // namespace Catch +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include + +namespace Catch +{ + +LeakDetector::LeakDetector() +{ + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); +} +} // namespace Catch + +#else + +Catch::LeakDetector::LeakDetector() +{ +} + +#endif +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include + +namespace Catch +{ + +std::size_t listTests(Config const &config); + +std::size_t listTestsNamesOnly(Config const &config); + +struct TagInfo +{ + void add(std::string const &spelling); + std::string all() const; + + std::set spellings; + std::size_t count = 0; +}; + +std::size_t listTags(Config const &config); + +std::size_t listReporters(Config const & /*config*/); + +Option list(Config const &config); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch +{ +using namespace clara::TextFlow; +} + +// end catch_text.h +#include +#include +#include + +namespace Catch +{ + +std::size_t listTests(Config const &config) +{ + TestSpec testSpec = config.testSpec(); + if (config.hasTestFilters()) + Catch::cout() << "Matching test cases:\n"; + else + { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + for (auto const &testCaseInfo : matchedTestCases) + { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard(colour); + + Catch::cout() << Column(testCaseInfo.name).initialIndent(2).indent(4) << "\n"; + if (config.verbosity() >= Verbosity::High) + { + Catch::cout() << Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if (description.empty()) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column(description).indent(4) << std::endl; + } + if (!testCaseInfo.tags.empty()) + Catch::cout() << Column(testCaseInfo.tagsAsString()).indent(6) << "\n"; + } + + if (!config.hasTestFilters()) + Catch::cout() << pluralise(matchedTestCases.size(), "test case") << '\n' + << std::endl; + else + Catch::cout() << pluralise(matchedTestCases.size(), "matching test case") << '\n' + << std::endl; + return matchedTestCases.size(); +} + +std::size_t listTestsNamesOnly(Config const &config) +{ + TestSpec testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + for (auto const &testCaseInfo : matchedTestCases) + { + matchedTests++; + if (startsWith(testCaseInfo.name, '#')) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if (config.verbosity() >= Verbosity::High) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; +} + +void TagInfo::add(std::string const &spelling) +{ + ++count; + spellings.insert(spelling); +} + +std::string TagInfo::all() const +{ + std::string out; + for (auto const &spelling : spellings) + out += "[" + spelling + "]"; + return out; +} + +std::size_t listTags(Config const &config) +{ + TestSpec testSpec = config.testSpec(); + if (config.hasTestFilters()) + Catch::cout() << "Tags for matching test cases:\n"; + else + { + Catch::cout() << "All available tags:\n"; + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + for (auto const &testCase : matchedTestCases) + { + for (auto const &tagName : testCase.getTestCaseInfo().tags) + { + std::string lcaseTagName = toLower(tagName); + auto countIt = tagCounts.find(lcaseTagName); + if (countIt == tagCounts.end()) + countIt = tagCounts.insert(std::make_pair(lcaseTagName, TagInfo())).first; + countIt->second.add(tagName); + } + } + + for (auto const &tagCount : tagCounts) + { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column(tagCount.second.all()) + .initialIndent(0) + .indent(str.size()) + .width(CATCH_CONFIG_CONSOLE_WIDTH - 10); + Catch::cout() << str << wrapper << '\n'; + } + Catch::cout() << pluralise(tagCounts.size(), "tag") << '\n' + << std::endl; + return tagCounts.size(); +} + +std::size_t listReporters(Config const & /*config*/) +{ + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const &factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for (auto const &factoryKvp : factories) + maxNameLen = (std::max)(maxNameLen, factoryKvp.first.size()); + + for (auto const &factoryKvp : factories) + { + Catch::cout() + << Column(factoryKvp.first + ":") + .indent(2) + .width(5 + maxNameLen) + + Column(factoryKvp.second->getDescription()) + .initialIndent(0) + .indent(2) + .width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); +} + +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 +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch +{ +namespace Matchers +{ +namespace Impl +{ + +std::string MatcherUntypedBase::toString() const +{ + if (m_cachedToString.empty()) + m_cachedToString = describe(); + return m_cachedToString; +} + +MatcherUntypedBase::~MatcherUntypedBase() = default; + +} // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_floating.cpp + +// start catch_to_string.hpp + +#include + +namespace Catch +{ +template +std::string to_string(T const &t) +{ +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif +} +} // end namespace Catch + +// end catch_to_string.hpp +#include +#include +#include + +namespace Catch +{ +namespace Matchers +{ +namespace Floating +{ +enum class FloatingPointKind : uint8_t +{ + Float, + Double +}; +} +} // namespace Matchers +} // namespace Catch + +namespace +{ + +template +struct Converter; + +template <> +struct Converter +{ + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) + { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter +{ + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) + { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template +auto convert(T t) -> Converter +{ + return Converter(t); +} + +template +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) +{ + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (std::isnan(lhs) || std::isnan(rhs)) + { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) + { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} // namespace + +namespace Catch +{ +namespace Matchers +{ +namespace Floating +{ +WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + : m_target{target}, m_margin{margin} +{ + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); +} + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool WithinAbsMatcher::match(double const &matchee) const +{ + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); +} + +std::string WithinAbsMatcher::describe() const +{ + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); +} + +WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + : m_target{target}, m_ulps{ulps}, m_type{baseType} +{ + CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.' + << " ULPs have to be non-negative."); +} + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + +bool WithinUlpsMatcher::match(double const &matchee) const +{ + switch (m_type) + { + case FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR("Unknown FloatingPointKind value"); + } +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +std::string WithinUlpsMatcher::describe() const +{ + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float) ? "f" : ""); +} + +} // namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) +{ + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) +{ + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) +{ + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string &desc) +{ + if (desc.empty()) + { + return "matches undescribed predicate"; + } + else + { + return "matches predicate: \"" + desc + '"'; + } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp + +#include + +namespace Catch +{ +namespace Matchers +{ + +namespace StdString +{ + +CasedString::CasedString(std::string const &str, CaseSensitive::Choice caseSensitivity) + : m_caseSensitivity(caseSensitivity), + m_str(adjustString(str)) +{ +} +std::string CasedString::adjustString(std::string const &str) const +{ + return m_caseSensitivity == CaseSensitive::No + ? toLower(str) + : str; +} +std::string CasedString::caseSensitivitySuffix() const +{ + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); +} + +StringMatcherBase::StringMatcherBase(std::string const &operation, CasedString const &comparator) + : m_comparator(comparator), + m_operation(operation) +{ +} + +std::string StringMatcherBase::describe() const +{ + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; +} + +EqualsMatcher::EqualsMatcher(CasedString const &comparator) : StringMatcherBase("equals", comparator) {} + +bool EqualsMatcher::match(std::string const &source) const +{ + return m_comparator.adjustString(source) == m_comparator.m_str; +} + +ContainsMatcher::ContainsMatcher(CasedString const &comparator) : StringMatcherBase("contains", comparator) {} + +bool ContainsMatcher::match(std::string const &source) const +{ + return contains(m_comparator.adjustString(source), m_comparator.m_str); +} + +StartsWithMatcher::StartsWithMatcher(CasedString const &comparator) : StringMatcherBase("starts with", comparator) {} + +bool StartsWithMatcher::match(std::string const &source) const +{ + return startsWith(m_comparator.adjustString(source), m_comparator.m_str); +} + +EndsWithMatcher::EndsWithMatcher(CasedString const &comparator) : StringMatcherBase("ends with", comparator) {} + +bool EndsWithMatcher::match(std::string const &source) const +{ + return endsWith(m_comparator.adjustString(source), m_comparator.m_str); +} + +RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity) : m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + +bool RegexMatcher::match(std::string const &matchee) const +{ + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) + { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); +} + +std::string RegexMatcher::describe() const +{ + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes) ? " case sensitively" : " case insensitively"); +} + +} // namespace StdString + +StdString::EqualsMatcher Equals(std::string const &str, CaseSensitive::Choice caseSensitivity) +{ + return StdString::EqualsMatcher(StdString::CasedString(str, caseSensitivity)); +} +StdString::ContainsMatcher Contains(std::string const &str, CaseSensitive::Choice caseSensitivity) +{ + return StdString::ContainsMatcher(StdString::CasedString(str, caseSensitivity)); +} +StdString::EndsWithMatcher EndsWith(std::string const &str, CaseSensitive::Choice caseSensitivity) +{ + return StdString::EndsWithMatcher(StdString::CasedString(str, caseSensitivity)); +} +StdString::StartsWithMatcher StartsWith(std::string const &str, CaseSensitive::Choice caseSensitivity) +{ + return StdString::StartsWithMatcher(StdString::CasedString(str, caseSensitivity)); +} + +StdString::RegexMatcher Matches(std::string const ®ex, CaseSensitive::Choice caseSensitivity) +{ + return StdString::RegexMatcher(regex, caseSensitivity); +} + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +// start catch_uncaught_exceptions.h + +namespace Catch +{ +bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +#include + +namespace Catch +{ + +MessageInfo::MessageInfo(StringRef const &_macroName, + SourceLineInfo const &_lineInfo, + ResultWas::OfType _type) + : macroName(_macroName), + lineInfo(_lineInfo), + type(_type), + sequence(++globalCount) +{ +} + +bool MessageInfo::operator==(MessageInfo const &other) const +{ + return sequence == other.sequence; +} + +bool MessageInfo::operator<(MessageInfo const &other) const +{ + return sequence < other.sequence; +} + +// This may need protecting if threading support is added +unsigned int MessageInfo::globalCount = 0; + +//////////////////////////////////////////////////////////////////////////// + +Catch::MessageBuilder::MessageBuilder(StringRef const ¯oName, + SourceLineInfo const &lineInfo, + ResultWas::OfType type) + : m_info(macroName, lineInfo, type) {} + +//////////////////////////////////////////////////////////////////////////// + +ScopedMessage::ScopedMessage(MessageBuilder const &builder) + : m_info(builder.m_info) +{ + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage(m_info); +} + +ScopedMessage::~ScopedMessage() +{ + if (!uncaught_exceptions()) + { + getResultCapture().popScopedMessage(m_info); + } +} + +Capturer::Capturer(StringRef macroName, SourceLineInfo const &lineInfo, ResultWas::OfType resultType, StringRef names) +{ + auto start = std::string::npos; + for (size_t pos = 0; pos <= names.size(); ++pos) + { + char c = names[pos]; + if (pos == names.size() || c == ' ' || c == '\t' || c == ',' || c == ']') + { + if (start != std::string::npos) + { + m_messages.push_back(MessageInfo(macroName, lineInfo, resultType)); + m_messages.back().message = names.substr(start, pos - start) + " := "; + start = std::string::npos; + } + } + else if (c != '[' && c != ']' && start == std::string::npos) + start = pos; + } +} +Capturer::~Capturer() +{ + if (!uncaught_exceptions()) + { + assert(m_captured == m_messages.size()); + for (size_t i = 0; i < m_captured; ++i) + m_resultCapture.popScopedMessage(m_messages[i]); + } +} + +void Capturer::captureValue(size_t index, StringRef value) +{ + assert(index < m_messages.size()); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage(m_messages[index]); + m_captured++; +} + +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include +#include +#include + +namespace Catch +{ + +class RedirectedStream +{ + std::ostream &m_originalStream; + std::ostream &m_redirectionStream; + std::streambuf *m_prevBuf; + +public: + RedirectedStream(std::ostream &originalStream, std::ostream &redirectionStream); + ~RedirectedStream(); +}; + +class RedirectedStdOut +{ + ReusableStringStream m_rss; + RedirectedStream m_cout; + +public: + RedirectedStdOut(); + auto str() const -> std::string; +}; + +// StdErr has two constituent streams in C++, std::cerr and std::clog +// This means that we need to redirect 2 streams into 1 to keep proper +// order of writes +class RedirectedStdErr +{ + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + +public: + RedirectedStdErr(); + auto str() const -> std::string; +}; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +// Windows's implementation of std::tmpfile is terrible (it tries +// to create a file inside system folder, thus requiring elevated +// privileges for the binary), so we have to use tmpnam(_s) and +// create the file ourselves there. +class TempFile +{ +public: + TempFile(TempFile const &) = delete; + TempFile &operator=(TempFile const &) = delete; + TempFile(TempFile &&) = delete; + TempFile &operator=(TempFile &&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE *getFile(); + std::string getContents(); + +private: + std::FILE *m_file = nullptr; +#if defined(_MSC_VER) + char m_buffer[L_tmpnam] = {0}; +#endif +}; + +class OutputRedirect +{ +public: + OutputRedirect(OutputRedirect const &) = delete; + OutputRedirect &operator=(OutputRedirect const &) = delete; + OutputRedirect(OutputRedirect &&) = delete; + OutputRedirect &operator=(OutputRedirect &&) = delete; + + OutputRedirect(std::string &stdout_dest, std::string &stderr_dest); + ~OutputRedirect(); + +private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string &m_stdoutDest; + std::string &m_stderrDest; +}; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) +#if defined(_MSC_VER) +#include //_dup and _dup2 +#define dup _dup +#define dup2 _dup2 +#define fileno _fileno +#else +#include // dup and dup2 +#endif +#endif + +namespace Catch +{ + +RedirectedStream::RedirectedStream(std::ostream &originalStream, std::ostream &redirectionStream) + : m_originalStream(originalStream), + m_redirectionStream(redirectionStream), + m_prevBuf(m_originalStream.rdbuf()) +{ + m_originalStream.rdbuf(m_redirectionStream.rdbuf()); +} + +RedirectedStream::~RedirectedStream() +{ + m_originalStream.rdbuf(m_prevBuf); +} + +RedirectedStdOut::RedirectedStdOut() : m_cout(Catch::cout(), m_rss.get()) {} +auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + +RedirectedStdErr::RedirectedStdErr() + : m_cerr(Catch::cerr(), m_rss.get()), + m_clog(Catch::clog(), m_rss.get()) +{ +} +auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) +TempFile::TempFile() +{ + if (tmpnam_s(m_buffer)) + { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) + { + char buffer[100]; + if (strerror_s(buffer, errno)) + { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer); + } +} +#else +TempFile::TempFile() +{ + m_file = std::tmpfile(); + if (!m_file) + { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } +} + +#endif + +TempFile::~TempFile() +{ + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif +} + +FILE *TempFile::getFile() +{ + return m_file; +} + +std::string TempFile::getContents() +{ + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) + { + sstr << buffer; + } + return sstr.str(); +} + +OutputRedirect::OutputRedirect(std::string &stdout_dest, std::string &stderr_dest) : m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) +{ + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); +} + +OutputRedirect::~OutputRedirect() +{ + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); +} + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) +#if defined(_MSC_VER) +#undef dup +#undef dup2 +#undef fileno +#endif +#endif +// end catch_output_redirect.cpp +// start catch_random_number_generator.cpp + +namespace Catch +{ + +std::mt19937 &rng() +{ + static std::mt19937 s_rng; + return s_rng; +} + +void seedRng(IConfig const &config) +{ + if (config.rngSeed() != 0) + { + std::srand(config.rngSeed()); + rng().seed(config.rngSeed()); + } +} + +unsigned int rngSeed() +{ + return getCurrentContext().getConfig()->rngSeed(); +} +} // namespace Catch +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include +#include +#include +#include + +namespace Catch +{ + +class TestCase; +struct IConfig; + +std::vector sortTests(IConfig const &config, std::vector const &unsortedTestCases); +bool matchTest(TestCase const &testCase, TestSpec const &testSpec, IConfig const &config); + +void enforceNoDuplicateTestCases(std::vector const &functions); + +std::vector filterTests(std::vector const &testCases, TestSpec const &testSpec, IConfig const &config); +std::vector const &getAllTestCasesSorted(IConfig const &config); + +class TestRegistry : public ITestCaseRegistry +{ +public: + virtual ~TestRegistry() = default; + + virtual void registerTest(TestCase const &testCase); + + std::vector const &getAllTests() const override; + std::vector const &getAllTestsSorted(IConfig const &config) const override; + +private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised +}; + +/////////////////////////////////////////////////////////////////////////// + +class TestInvokerAsFunction : public ITestInvoker +{ + void (*m_testAsFunction)(); + +public: + TestInvokerAsFunction(void (*testAsFunction)()) noexcept; + + void invoke() const override; +}; + +std::string extractClassName(StringRef const &classOrQualifiedMethodName); + +/////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include + +namespace Catch +{ + +class ReporterRegistry : public IReporterRegistry +{ + +public: + ~ReporterRegistry() override; + + IStreamingReporterPtr create(std::string const &name, IConfigPtr const &config) const override; + + void registerReporter(std::string const &name, IReporterFactoryPtr const &factory); + void registerListener(IReporterFactoryPtr const &factory); + + FactoryMap const &getFactories() const override; + Listeners const &getListeners() const override; + +private: + FactoryMap m_factories; + Listeners m_listeners; +}; +} // namespace Catch + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include + +namespace Catch +{ + +struct TagAlias +{ + TagAlias(std::string const &_tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; +}; + +} // end namespace Catch + +// end catch_tag_alias.h +#include + +namespace Catch +{ + +class TagAliasRegistry : public ITagAliasRegistry +{ +public: + ~TagAliasRegistry() override; + TagAlias const *find(std::string const &alias) const override; + std::string expandAliases(std::string const &unexpandedTestSpec) const override; + void add(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo); + +private: + std::map m_registry; +}; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include +#include + +namespace Catch +{ + +class StartupExceptionRegistry +{ +public: + void add(std::exception_ptr const &exception) noexcept; + std::vector const &getExceptions() const noexcept; + +private: + std::vector m_exceptions; +}; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +// start catch_singletons.hpp + +namespace Catch +{ + +struct ISingleton +{ + virtual ~ISingleton(); +}; + +void addSingleton(ISingleton *singleton); +void cleanupSingletons(); + +template +class Singleton : SingletonImplT, public ISingleton +{ + + static auto getInternal() -> Singleton * + { + static Singleton *s_instance = nullptr; + if (!s_instance) + { + s_instance = new Singleton; + addSingleton(s_instance); + } + return s_instance; + } + +public: + static auto get() -> InterfaceT const & + { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT & + { + return *getInternal(); + } +}; + +} // namespace Catch + +// end catch_singletons.hpp +namespace Catch +{ + +namespace +{ + +class RegistryHub : public IRegistryHub, public IMutableRegistryHub, private NonCopyable +{ + +public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const &getReporterRegistry() const override + { + return m_reporterRegistry; + } + ITestCaseRegistry const &getTestCaseRegistry() const override + { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const &getExceptionTranslatorRegistry() const override + { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const &getTagAliasRegistry() const override + { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const &getStartupExceptionRegistry() const override + { + return m_exceptionRegistry; + } + +public: // IMutableRegistryHub + void registerReporter(std::string const &name, IReporterFactoryPtr const &factory) override + { + m_reporterRegistry.registerReporter(name, factory); + } + void registerListener(IReporterFactoryPtr const &factory) override + { + m_reporterRegistry.registerListener(factory); + } + void registerTest(TestCase const &testInfo) override + { + m_testCaseRegistry.registerTest(testInfo); + } + void registerTranslator(const IExceptionTranslator *translator) override + { + m_exceptionTranslatorRegistry.registerTranslator(translator); + } + void registerTagAlias(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) override + { + m_tagAliasRegistry.add(alias, tag, lineInfo); + } + void registerStartupException() noexcept override + { + m_exceptionRegistry.add(std::current_exception()); + } + +private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; +}; +} // namespace + +using RegistryHubSingleton = Singleton; + +IRegistryHub const &getRegistryHub() +{ + return RegistryHubSingleton::get(); +} +IMutableRegistryHub &getMutableRegistryHub() +{ + return RegistryHubSingleton::getMutable(); +} +void cleanUp() +{ + cleanupSingletons(); + cleanUpContext(); +} +std::string translateActiveException() +{ + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); +} + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch +{ + +ReporterRegistry::~ReporterRegistry() = default; + +IStreamingReporterPtr ReporterRegistry::create(std::string const &name, IConfigPtr const &config) const +{ + auto it = m_factories.find(name); + if (it == m_factories.end()) + return nullptr; + return it->second->create(ReporterConfig(config)); +} + +void ReporterRegistry::registerReporter(std::string const &name, IReporterFactoryPtr const &factory) +{ + m_factories.emplace(name, factory); +} +void ReporterRegistry::registerListener(IReporterFactoryPtr const &factory) +{ + m_listeners.push_back(factory); +} + +IReporterRegistry::FactoryMap const &ReporterRegistry::getFactories() const +{ + return m_factories; +} +IReporterRegistry::Listeners const &ReporterRegistry::getListeners() const +{ + return m_listeners; +} + +} // namespace Catch +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch +{ + +bool isOk(ResultWas::OfType resultType) +{ + return (resultType & ResultWas::FailureBit) == 0; +} +bool isJustInfo(int flags) +{ + return flags == ResultWas::Info; +} + +ResultDisposition::Flags operator|(ResultDisposition::Flags lhs, ResultDisposition::Flags rhs) +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +bool shouldContinueOnFailure(int flags) { return (flags & ResultDisposition::ContinueOnFailure) != 0; } +bool shouldSuppressFailure(int flags) { return (flags & ResultDisposition::SuppressFail) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp + +#include +#include +#include + +namespace Catch +{ + +namespace Generators +{ +struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker +{ + size_t m_index = static_cast(-1); + GeneratorBasePtr m_generator; + + GeneratorTracker(TestCaseTracking::NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent) + : TrackerBase(nameAndLocation, ctx, parent) + { + } + ~GeneratorTracker(); + + static GeneratorTracker &acquire(TrackerContext &ctx, TestCaseTracking::NameAndLocation const &nameAndLocation) + { + std::shared_ptr tracker; + + ITracker ¤tTracker = ctx.currentTracker(); + if (TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild(nameAndLocation)) + { + assert(childTracker); + assert(childTracker->isIndexTracker()); + tracker = std::static_pointer_cast(childTracker); + } + else + { + tracker = std::make_shared(nameAndLocation, ctx, ¤tTracker); + currentTracker.addChild(tracker); + } + + if (!ctx.completedCycle() && !tracker->isComplete()) + { + if (tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + void moveNext() + { + m_index++; + m_children.clear(); + } + + // TrackerBase interface + bool isIndexTracker() const override { return true; } + auto hasGenerator() const -> bool override + { + return !!m_generator; + } + void close() override + { + TrackerBase::close(); + if (m_runState == CompletedSuccessfully && m_index < m_generator->size() - 1) + m_runState = Executing; + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const & override + { + return m_generator; + } + void setGenerator(GeneratorBasePtr &&generator) override + { + m_generator = std::move(generator); + } + auto getIndex() const -> size_t override + { + return m_index; + } +}; +GeneratorTracker::~GeneratorTracker() {} +} // namespace Generators + +RunContext::RunContext(IConfigPtr const &_config, IStreamingReporterPtr &&reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{StringRef(), SourceLineInfo("", 0), StringRef(), ResultDisposition::Normal}, + m_includeSuccessfulResults(m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions) +{ + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); +} + +RunContext::~RunContext() +{ + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); +} + +void RunContext::testGroupStarting(std::string const &testSpec, std::size_t groupIndex, std::size_t groupsCount) +{ + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); +} + +void RunContext::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 RunContext::runTest(TestCase const &testCase) +{ + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const &testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker &rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do + { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) + { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; +} + +IConfigPtr RunContext::config() const +{ + return m_config; +} + +IStreamingReporter &RunContext::reporter() const +{ + return *m_reporter; +} + +void RunContext::assertionEnded(AssertionResult const &result) +{ + if (result.getResultType() == ResultWas::Ok) + { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } + else if (!result.isOk()) + { + m_lastAssertionPassed = false; + if (m_activeTestCase->getTestCaseInfo().okToFail()) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else + { + m_lastAssertionPassed = true; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; +} +void RunContext::resetAssertionInfo() +{ + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; +} + +bool RunContext::sectionStarted(SectionInfo const §ionInfo, Counts &assertions) +{ + ITracker §ionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; +} +auto RunContext::acquireGeneratorTracker(SourceLineInfo const &lineInfo) -> IGeneratorTracker & +{ + using namespace Generators; + GeneratorTracker &tracker = GeneratorTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation("generator", lineInfo)); + assert(tracker.isOpen()); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; +} + +bool RunContext::testForMissingAssertions(Counts &assertions) +{ + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; +} + +void RunContext::sectionEnded(SectionEndInfo const &endInfo) +{ + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) + { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); +} + +void RunContext::sectionEndedEarly(SectionEndInfo const &endInfo) +{ + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); +} +void RunContext::benchmarkStarting(BenchmarkInfo const &info) +{ + m_reporter->benchmarkStarting(info); +} +void RunContext::benchmarkEnded(BenchmarkStats const &stats) +{ + m_reporter->benchmarkEnded(stats); +} + +void RunContext::pushScopedMessage(MessageInfo const &message) +{ + m_messages.push_back(message); +} + +void RunContext::popScopedMessage(MessageInfo const &message) +{ + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); +} + +std::string RunContext::getCurrentTestName() const +{ + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); +} + +const AssertionResult *RunContext::getLastResult() const +{ + return &(*m_lastResult); +} + +void RunContext::exceptionEarlyReported() +{ + m_shouldReportUnexpected = false; +} + +void RunContext::handleFatalErrorCondition(StringRef message) +{ + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult(ResultWas::FatalErrorCondition, {false}); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const &testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); +} + +bool RunContext::lastAssertionPassed() +{ + return m_lastAssertionPassed; +} + +void RunContext::assertionPassed() +{ + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); +} + +bool RunContext::aborting() const +{ + return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); +} + +void RunContext::runCurrentTest(std::string &redirectedCout, std::string &redirectedCerr) +{ + auto const &testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = {"TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal}; + + seedRng(*m_config); + + Timer timer; + CATCH_TRY + { + if (m_reporter->getPreferences().shouldRedirectStdOut) + { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStdOut redirectedStdOut; + RedirectedStdErr redirectedStdErr; + + timer.start(); + invokeActiveTestCase(); + redirectedCout += redirectedStdOut.str(); + redirectedCerr += redirectedStdErr.str(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } + else + { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + CATCH_CATCH_ANON(TestFailureException &) + { + // This just means the test was aborted due to failure + } + CATCH_CATCH_ALL + { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if (m_shouldReportUnexpected) + { + AssertionReaction dummyReaction; + handleUnexpectedInflightException(m_lastAssertionInfo, translateActiveException(), dummyReaction); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); +} + +void RunContext::invokeActiveTestCase() +{ + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); +} + +void RunContext::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 (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); +} + +void RunContext::handleExpr( + AssertionInfo const &info, + ITransientExpression const &expr, + AssertionReaction &reaction) +{ + m_reporter->assertionStarting(info); + + bool negated = isFalseTest(info.resultDisposition); + bool result = expr.getResult() != negated; + + if (result) + { + if (!m_includeSuccessfulResults) + { + assertionPassed(); + } + else + { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else + { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated); + populateReaction(reaction); + } +} +void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated) +{ + + m_lastAssertionInfo = info; + AssertionResultData data(resultType, LazyExpression(negated)); + + AssertionResult assertionResult{info, data}; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded(assertionResult); +} + +void RunContext::handleMessage( + AssertionInfo const &info, + ResultWas::OfType resultType, + StringRef const &message, + AssertionReaction &reaction) +{ + m_reporter->assertionStarting(info); + + m_lastAssertionInfo = info; + + AssertionResultData data(resultType, LazyExpression(false)); + data.message = message; + AssertionResult assertionResult{m_lastAssertionInfo, data}; + assertionEnded(assertionResult); + if (!assertionResult.isOk()) + populateReaction(reaction); +} +void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const &info, + AssertionReaction &reaction) +{ + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); +} + +void RunContext::handleUnexpectedInflightException( + AssertionInfo const &info, + std::string const &message, + AssertionReaction &reaction) +{ + m_lastAssertionInfo = info; + + AssertionResultData data(ResultWas::ThrewException, LazyExpression(false)); + data.message = message; + AssertionResult assertionResult{info, data}; + assertionEnded(assertionResult); + populateReaction(reaction); +} + +void RunContext::populateReaction(AssertionReaction &reaction) +{ + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); +} + +void RunContext::handleIncomplete( + AssertionInfo const &info) +{ + m_lastAssertionInfo = info; + + AssertionResultData data(ResultWas::ThrewException, LazyExpression(false)); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{info, data}; + assertionEnded(assertionResult); +} +void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction) +{ + m_lastAssertionInfo = info; + + AssertionResultData data(resultType, LazyExpression(false)); + AssertionResult assertionResult{info, data}; + assertionEnded(assertionResult); + + if (!assertionResult.isOk()) + populateReaction(reaction); +} + +IResultCapture &getResultCapture() +{ + if (auto *capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); +} +} // namespace Catch +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch +{ + +Section::Section(SectionInfo const &info) + : m_info(info), + m_sectionIncluded(getResultCapture().sectionStarted(m_info, m_assertions)) +{ + m_timer.start(); +} + +Section::~Section() +{ + if (m_sectionIncluded) + { + SectionEndInfo endInfo{m_info, m_assertions, m_timer.getElapsedSeconds()}; + if (uncaught_exceptions()) + getResultCapture().sectionEndedEarly(endInfo); + else + getResultCapture().sectionEnded(endInfo); + } +} + +// This indicates whether the section should be executed or not +Section::operator bool() const +{ + return m_sectionIncluded; +} + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch +{ + +SectionInfo::SectionInfo(SourceLineInfo const &_lineInfo, + std::string const &_name) + : name(_name), + lineInfo(_lineInfo) +{ +} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include + +namespace Catch +{ + +class Session : NonCopyable +{ +public: + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine(int argc, char const *const *argv); + + void useConfigData(ConfigData const &configData); + + int run(int argc, char *argv[]); +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int run(int argc, wchar_t *const argv[]); +#endif + int run(); + + clara::Parser const &cli() const; + void cli(clara::Parser const &newParser); + ConfigData &configData(); + Config &config(); + +private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr m_config; + bool m_startupExceptions = false; +}; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include + +namespace Catch +{ + +// Versioning information +struct Version +{ + Version(Version const &) = delete; + Version &operator=(Version const &) = delete; + Version(unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const *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 + char const *const branchName; + unsigned int const buildNumber; + + friend std::ostream &operator<<(std::ostream &os, Version const &version); +}; + +Version const &libraryVersion(); +} // namespace Catch + +// end catch_version.h +#include +#include + +namespace Catch +{ + +namespace +{ +const int MaxExitCode = 255; + +IStreamingReporterPtr createReporter(std::string const &reporterName, IConfigPtr const &config) +{ + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; +} + +IStreamingReporterPtr makeReporter(std::shared_ptr const &config) +{ + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) + { + return createReporter(config->getReporterName(), config); + } + + auto multi = std::unique_ptr(new ListeningReporter); + + auto const &listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const &listener : listeners) + { + multi->addListener(listener->create(Catch::ReporterConfig(config))); + } + multi->addReporter(createReporter(config->getReporterName(), config)); + return std::move(multi); +} + +Catch::Totals runTests(std::shared_ptr const &config) +{ + // FixMe: Add listeners in order first, then add reporters. + + auto reporter = makeReporter(config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + + auto const &allTestCases = getAllTestCasesSorted(*config); + for (auto const &testCase : allTestCases) + { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + if (config->warnAboutNoTests() && totals.testCases.total() == 0) + { + ReusableStringStream testConfig; + + bool first = true; + for (const auto &input : config->getTestsOrTags()) + { + if (!first) + { + testConfig << ' '; + } + first = false; + testConfig << input; + } + + context.reporter().noMatchingTestCases(testConfig.str()); + totals.error = -1; + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; +} + +void applyFilenamesAsTags(Catch::IConfig const &config) +{ + auto &tests = const_cast &>(getAllTestCasesSorted(config)); + for (auto &testCase : tests) + { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) + { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) + { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } +} + +} // namespace + +Session::Session() +{ + static bool alreadyInstantiated = false; + if (alreadyInstantiated) + { + CATCH_TRY { CATCH_INTERNAL_ERROR("Only one instance of Catch::Session can ever be used"); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto &exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if (!exceptions.empty()) + { + m_startupExceptions = true; + Colour colourGuard(Colour::Red); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for (const auto &ex_ptr : exceptions) + { + try + { + std::rethrow_exception(ex_ptr); + } + catch (std::exception const &ex) + { + Catch::cerr() << Column(ex.what()).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser(m_configData); +} +Session::~Session() +{ + Catch::cleanUp(); +} + +void Session::showHelp() const +{ + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" + << std::endl; +} +void Session::libIdentify() +{ + Catch::cout() + << std::left << std::setw(16) << "description: " + << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " + << "testframework\n" + << std::left << std::setw(16) << "framework: " + << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; +} + +int Session::applyCommandLine(int argc, char const *const *argv) +{ + if (m_startupExceptions) + return 1; + + auto result = m_cli.parse(clara::Args(argc, argv)); + if (!result) + { + Catch::cerr() + << Colour(Colour::Red) + << "\nError(s) in input:\n" + << Column(result.errorMessage()).indent(2) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" + << std::endl; + return MaxExitCode; + } + + if (m_configData.showHelp) + showHelp(); + if (m_configData.libIdentify) + libIdentify(); + m_config.reset(); + return 0; +} + +void Session::useConfigData(ConfigData const &configData) +{ + m_configData = configData; + m_config.reset(); +} + +int Session::run(int argc, char *argv[]) +{ + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; +} + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) +int Session::run(int argc, wchar_t *const argv[]) +{ + + char **utf8Argv = new char *[argc]; + + for (int i = 0; i < argc; ++i) + { + int bufSize = WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL); + + utf8Argv[i] = new char[bufSize]; + + WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL); + } + + int returnCode = run(argc, utf8Argv); + + for (int i = 0; i < argc; ++i) + delete[] utf8Argv[i]; + + delete[] utf8Argv; + + return returnCode; +} +#endif +int Session::run() +{ + if ((m_configData.waitForKeypress & WaitForKeypress::BeforeStart) != 0) + { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + if ((m_configData.waitForKeypress & WaitForKeypress::BeforeExit) != 0) + { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast(std::getchar()); + } + return exitCode; +} + +clara::Parser const &Session::cli() const +{ + return m_cli; +} +void Session::cli(clara::Parser const &newParser) +{ + m_cli = newParser; +} +ConfigData &Session::configData() +{ + return m_configData; +} +Config &Session::config() +{ + if (!m_config) + m_config = std::make_shared(m_configData); + return *m_config; +} + +int Session::runInternal() +{ + if (m_startupExceptions) + return 1; + + if (m_configData.showHelp || m_configData.libIdentify) + { + return 0; + } + + CATCH_TRY + { + config(); // Force config to be constructed + + seedRng(*m_config); + + if (m_configData.filenamesAsTags) + applyFilenamesAsTags(*m_config); + + // Handle list request + if (Option listed = list(config())) + return static_cast(*listed); + + auto totals = runTests(m_config); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min)(MaxExitCode, (std::max)(totals.error, static_cast(totals.assertions.failed))); + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch (std::exception &ex) + { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } +#endif +} + +} // end namespace Catch +// end catch_session.cpp +// start catch_singletons.cpp + +#include + +namespace Catch +{ + +namespace +{ +static auto getSingletons() -> std::vector *& +{ + static std::vector *g_singletons = nullptr; + if (!g_singletons) + g_singletons = new std::vector(); + return g_singletons; +} +} // namespace + +ISingleton::~ISingleton() {} + +void addSingleton(ISingleton *singleton) +{ + getSingletons()->push_back(singleton); +} +void cleanupSingletons() +{ + auto &singletons = getSingletons(); + for (auto singleton : *singletons) + delete singleton; + delete singletons; + singletons = nullptr; +} + +} // namespace Catch +// end catch_singletons.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch +{ +void StartupExceptionRegistry::add(std::exception_ptr const &exception) noexcept +{ + CATCH_TRY + { + m_exceptions.push_back(exception); + } + CATCH_CATCH_ALL + { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } +} + +std::vector const &StartupExceptionRegistry::getExceptions() const noexcept +{ + return m_exceptions; +} + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include +#include +#include +#include +#include +#include + +namespace Catch +{ + +Catch::IStream::~IStream() = default; + +namespace detail +{ +namespace +{ +template +class StreamBufImpl : public std::streambuf +{ + char data[bufferSize]; + WriterF m_writer; + +public: + StreamBufImpl() + { + setp(data, data + sizeof(data)); + } + + ~StreamBufImpl() noexcept + { + StreamBufImpl::sync(); + } + +private: + int overflow(int c) override + { + sync(); + + if (c != EOF) + { + if (pbase() == epptr()) + m_writer(std::string(1, static_cast(c))); + else + sputc(static_cast(c)); + } + return 0; + } + + int sync() override + { + 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); + } +}; + +/////////////////////////////////////////////////////////////////////////// + +class FileStream : public IStream +{ + mutable std::ofstream m_ofs; + +public: + FileStream(StringRef filename) + { + m_ofs.open(filename.c_str()); + CATCH_ENFORCE(!m_ofs.fail(), "Unable to open file: '" << filename << "'"); + } + ~FileStream() override = default; + +public: // IStream + std::ostream &stream() const override + { + return m_ofs; + } +}; + +/////////////////////////////////////////////////////////////////////////// + +class CoutStream : public IStream +{ + mutable std::ostream m_os; + +public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os(Catch::cout().rdbuf()) {} + ~CoutStream() override = default; + +public: // IStream + std::ostream &stream() const override { return m_os; } +}; + +/////////////////////////////////////////////////////////////////////////// + +class DebugOutStream : public IStream +{ + std::unique_ptr> m_streamBuf; + mutable std::ostream m_os; + +public: + DebugOutStream() + : m_streamBuf(new StreamBufImpl()), + m_os(m_streamBuf.get()) + { + } + + ~DebugOutStream() override = default; + +public: // IStream + std::ostream &stream() const override { return m_os; } +}; + +} // namespace +} // namespace detail + +/////////////////////////////////////////////////////////////////////////// + +auto makeStream(StringRef const &filename) -> IStream const * +{ + if (filename.empty()) + return new detail::CoutStream(); + else if (filename[0] == '%') + { + if (filename == "%debug") + return new detail::DebugOutStream(); + else + CATCH_ERROR("Unrecognised stream: '" << filename << "'"); + } + else + return new detail::FileStream(filename); +} + +// This class encapsulates the idea of a pool of ostringstreams that can be reused. +struct StringStreams +{ + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t + { + if (m_unused.empty()) + { + m_streams.push_back(std::unique_ptr(new std::ostringstream)); + return m_streams.size() - 1; + } + else + { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release(std::size_t index) + { + m_streams[index]->copyfmt(m_referenceStream); // Restore initial flags and other state + m_unused.push_back(index); + } +}; + +ReusableStringStream::ReusableStringStream() + : m_index(Singleton::getMutable().add()), + m_oss(Singleton::getMutable().m_streams[m_index].get()) +{ +} + +ReusableStringStream::~ReusableStringStream() +{ + static_cast(m_oss)->str(""); + m_oss->clear(); + Singleton::getMutable().release(m_index); +} + +auto ReusableStringStream::str() const -> std::string +{ + return static_cast(m_oss)->str(); +} + +/////////////////////////////////////////////////////////////////////////// + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions +std::ostream &cout() +{ + return std::cout; +} +std::ostream &cerr() { return std::cerr; } +std::ostream &clog() { return std::clog; } +#endif +} // namespace Catch +// end catch_stream.cpp +// start catch_string_manip.cpp + +#include +#include +#include +#include + +namespace Catch +{ + +namespace +{ +char toLowerCh(char c) +{ + return static_cast(std::tolower(c)); +} +} // namespace + +bool startsWith(std::string const &s, std::string const &prefix) +{ + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); +} +bool startsWith(std::string const &s, char prefix) +{ + return !s.empty() && s[0] == prefix; +} +bool endsWith(std::string const &s, std::string const &suffix) +{ + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); +} +bool endsWith(std::string const &s, char suffix) +{ + return !s.empty() && s[s.size() - 1] == 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(), toLowerCh); +} +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) : std::string(); +} + +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; +} + +} // namespace Catch +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include +#include +#include + +namespace +{ +const uint32_t byte_2_lead = 0xC0; +const uint32_t byte_3_lead = 0xE0; +const uint32_t byte_4_lead = 0xF0; +} // namespace + +namespace Catch +{ +StringRef::StringRef(char const *rawChars) noexcept + : StringRef(rawChars, static_cast(std::strlen(rawChars))) +{ +} + +StringRef::operator std::string() const +{ + return std::string(m_start, m_size); +} + +void StringRef::swap(StringRef &other) noexcept +{ + std::swap(m_start, other.m_start); + std::swap(m_size, other.m_size); + std::swap(m_data, other.m_data); +} + +auto StringRef::c_str() const -> char const * +{ + if (isSubstring()) + const_cast(this)->takeOwnership(); + return m_start; +} +auto StringRef::currentData() const noexcept -> char const * +{ + return m_start; +} + +auto StringRef::isOwned() const noexcept -> bool +{ + return m_data != nullptr; +} +auto StringRef::isSubstring() const noexcept -> bool +{ + return m_start[m_size] != '\0'; +} + +void StringRef::takeOwnership() +{ + if (!isOwned()) + { + m_data = new char[m_size + 1]; + memcpy(m_data, m_start, m_size); + m_data[m_size] = '\0'; + m_start = m_data; + } +} +auto StringRef::substr(size_type start, size_type size) const noexcept -> StringRef +{ + if (start < m_size) + return StringRef(m_start + start, size); + else + return StringRef(); +} +auto StringRef::operator==(StringRef const &other) const noexcept -> bool +{ + return size() == other.size() && + (std::strncmp(m_start, other.m_start, size()) == 0); +} +auto StringRef::operator!=(StringRef const &other) const noexcept -> bool +{ + return !operator==(other); +} + +auto StringRef::operator[](size_type index) const noexcept -> char +{ + return m_start[index]; +} + +auto StringRef::numberOfCharacters() const noexcept -> size_type +{ + size_type noChars = m_size; + // Make adjustments for uft encodings + for (size_type i = 0; i < m_size; ++i) + { + char c = m_start[i]; + if ((c & byte_2_lead) == byte_2_lead) + { + noChars--; + if ((c & byte_3_lead) == byte_3_lead) + noChars--; + if ((c & byte_4_lead) == byte_4_lead) + noChars--; + } + } + return noChars; +} + +auto operator+(StringRef const &lhs, StringRef const &rhs) -> std::string +{ + std::string str; + str.reserve(lhs.size() + rhs.size()); + str += lhs; + str += rhs; + return str; +} +auto operator+(StringRef const &lhs, const char *rhs) -> std::string +{ + return std::string(lhs) + std::string(rhs); +} +auto operator+(char const *lhs, StringRef const &rhs) -> std::string +{ + return std::string(lhs) + std::string(rhs); +} + +auto operator<<(std::ostream &os, StringRef const &str) -> std::ostream & +{ + return os.write(str.currentData(), str.size()); +} + +auto operator+=(std::string &lhs, StringRef const &rhs) -> std::string & +{ + lhs.append(rhs.currentData(), rhs.size()); + return lhs; +} + +} // namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch +{ +TagAlias::TagAlias(std::string const &_tag, SourceLineInfo _lineInfo) : tag(_tag), lineInfo(_lineInfo) {} +} // namespace Catch +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch +{ + +RegistrarForTagAliases::RegistrarForTagAliases(char const *alias, char const *tag, SourceLineInfo const &lineInfo) +{ + CATCH_TRY + { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } + CATCH_CATCH_ALL + { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } +} + +} // namespace Catch +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include + +namespace Catch +{ + +TagAliasRegistry::~TagAliasRegistry() {} + +TagAlias const *TagAliasRegistry::find(std::string const &alias) const +{ + auto it = m_registry.find(alias); + if (it != m_registry.end()) + return &(it->second); + else + return nullptr; +} + +std::string TagAliasRegistry::expandAliases(std::string const &unexpandedTestSpec) const +{ + std::string expandedTestSpec = unexpandedTestSpec; + for (auto const ®istryKvp : m_registry) + { + std::size_t pos = expandedTestSpec.find(registryKvp.first); + if (pos != std::string::npos) + { + expandedTestSpec = expandedTestSpec.substr(0, pos) + + registryKvp.second.tag + + expandedTestSpec.substr(pos + registryKvp.first.size()); + } + } + return expandedTestSpec; +} + +void TagAliasRegistry::add(std::string const &alias, std::string const &tag, SourceLineInfo const &lineInfo) +{ + CATCH_ENFORCE(startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" + << lineInfo); + + CATCH_ENFORCE(m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo); +} + +ITagAliasRegistry::~ITagAliasRegistry() {} + +ITagAliasRegistry const &ITagAliasRegistry::get() +{ + return getRegistryHub().getTagAliasRegistry(); +} + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include +#include +#include +#include + +namespace Catch +{ + +namespace +{ +TestCaseInfo::SpecialProperties parseSpecialTag(std::string const &tag) +{ + if (startsWith(tag, '.') || + 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 if (tag == "!nonportable") + return TestCaseInfo::NonPortable; + else if (tag == "!benchmark") + return static_cast(TestCaseInfo::Benchmark | TestCaseInfo::IsHidden); + else + return TestCaseInfo::None; +} +bool isReservedTag(std::string const &tag) +{ + return parseSpecialTag(tag) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum(static_cast(tag[0])); +} +void enforceNotReservedTag(std::string const &tag, SourceLineInfo const &_lineInfo) +{ + CATCH_ENFORCE(!isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo); +} +} // namespace + +TestCase makeTestCase(ITestInvoker *_testCase, + std::string const &_className, + NameAndTags const &nameAndTags, + SourceLineInfo const &_lineInfo) +{ + bool isHidden = false; + + // Parse out tags + std::vector tags; + std::string desc, tag; + bool inTag = false; + std::string _descOrTags = nameAndTags.tags; + for (char c : _descOrTags) + { + if (!inTag) + { + if (c == '[') + inTag = true; + else + desc += c; + } + else + { + if (c == ']') + { + TestCaseInfo::SpecialProperties prop = parseSpecialTag(tag); + if ((prop & TestCaseInfo::IsHidden) != 0) + isHidden = true; + else if (prop == TestCaseInfo::None) + enforceNotReservedTag(tag, _lineInfo); + + tags.push_back(tag); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if (isHidden) + { + tags.push_back("."); + } + + TestCaseInfo info(nameAndTags.name, _className, desc, tags, _lineInfo); + return TestCase(_testCase, std::move(info)); +} + +void setTags(TestCaseInfo &testCaseInfo, std::vector tags) +{ + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for (auto const &tag : tags) + { + std::string lcaseTag = toLower(tag); + testCaseInfo.properties = static_cast(testCaseInfo.properties | parseSpecialTag(lcaseTag)); + testCaseInfo.lcaseTags.push_back(lcaseTag); + } + testCaseInfo.tags = std::move(tags); +} + +TestCaseInfo::TestCaseInfo(std::string const &_name, + std::string const &_className, + std::string const &_description, + std::vector const &_tags, + SourceLineInfo const &_lineInfo) + : name(_name), + className(_className), + description(_description), + lineInfo(_lineInfo), + properties(None) +{ + setTags(*this, _tags); +} + +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; +} + +std::string TestCaseInfo::tagsAsString() const +{ + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto &tag : tags) + { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto &tag : tags) + { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; +} + +TestCase::TestCase(ITestInvoker *testCase, TestCaseInfo &&info) : TestCaseInfo(std::move(info)), test(testCase) {} + +TestCase TestCase::withName(std::string const &_newName) const +{ + TestCase other(*this); + other.name = _newName; + return other; +} + +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; +} + +TestCaseInfo const &TestCase::getTestCaseInfo() const +{ + return *this; +} + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include + +namespace Catch +{ + +std::vector sortTests(IConfig const &config, std::vector const &unsortedTestCases) +{ + + std::vector sorted = unsortedTestCases; + + switch (config.runOrder()) + { + case RunTests::InLexicographicalOrder: + std::sort(sorted.begin(), sorted.end()); + break; + case RunTests::InRandomOrder: + seedRng(config); + std::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 (auto const &function : functions) + { + auto prev = seenFunctions.insert(function); + CATCH_ENFORCE(prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo); + } +} + +std::vector filterTests(std::vector const &testCases, TestSpec const &testSpec, IConfig const &config) +{ + std::vector filtered; + filtered.reserve(testCases.size()); + for (auto const &testCase : testCases) + if (matchTest(testCase, testSpec, config)) + filtered.push_back(testCase); + return filtered; +} +std::vector const &getAllTestCasesSorted(IConfig const &config) +{ + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted(config); +} + +void TestRegistry::registerTest(TestCase const &testCase) +{ + std::string name = testCase.getTestCaseInfo().name; + if (name.empty()) + { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest(testCase.withName(rss.str())); + } + m_functions.push_back(testCase); +} + +std::vector const &TestRegistry::getAllTests() const +{ + return m_functions; +} +std::vector const &TestRegistry::getAllTestsSorted(IConfig const &config) const +{ + if (m_sortedFunctions.empty()) + enforceNoDuplicateTestCases(m_functions); + + if (m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty()) + { + m_sortedFunctions = sortTests(config, m_functions); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; +} + +/////////////////////////////////////////////////////////////////////////// +TestInvokerAsFunction::TestInvokerAsFunction(void (*testAsFunction)()) noexcept : m_testAsFunction(testAsFunction) {} + +void TestInvokerAsFunction::invoke() const +{ + m_testAsFunction(); +} + +std::string extractClassName(StringRef 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; +} + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include +#include +#include +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch +{ +namespace TestCaseTracking +{ + +NameAndLocation::NameAndLocation(std::string const &_name, SourceLineInfo const &_location) + : name(_name), + location(_location) +{ +} + +ITracker::~ITracker() = default; + +TrackerContext &TrackerContext::instance() +{ + static TrackerContext s_instance; + return s_instance; +} + +ITracker &TrackerContext::startRun() +{ + m_rootTracker = std::make_shared(NameAndLocation("{root}", CATCH_INTERNAL_LINEINFO), *this, nullptr); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; +} + +void TrackerContext::endRun() +{ + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; +} + +void TrackerContext::startCycle() +{ + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; +} +void TrackerContext::completeCycle() +{ + m_runState = CompletedCycle; +} + +bool TrackerContext::completedCycle() const +{ + return m_runState == CompletedCycle; +} +ITracker &TrackerContext::currentTracker() +{ + return *m_currentTracker; +} +void TrackerContext::setCurrentTracker(ITracker *tracker) +{ + m_currentTracker = tracker; +} + +TrackerBase::TrackerBase(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent) + : m_nameAndLocation(nameAndLocation), + m_ctx(ctx), + m_parent(parent) +{ +} + +NameAndLocation const &TrackerBase::nameAndLocation() const +{ + return m_nameAndLocation; +} +bool TrackerBase::isComplete() const +{ + return m_runState == CompletedSuccessfully || m_runState == Failed; +} +bool TrackerBase::isSuccessfullyCompleted() const +{ + return m_runState == CompletedSuccessfully; +} +bool TrackerBase::isOpen() const +{ + return m_runState != NotStarted && !isComplete(); +} +bool TrackerBase::hasChildren() const +{ + return !m_children.empty(); +} + +void TrackerBase::addChild(ITrackerPtr const &child) +{ + m_children.push_back(child); +} + +ITrackerPtr TrackerBase::findChild(NameAndLocation const &nameAndLocation) +{ + auto it = std::find_if(m_children.begin(), m_children.end(), + [&nameAndLocation](ITrackerPtr const &tracker) { + return tracker->nameAndLocation().location == nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + }); + return (it != m_children.end()) + ? *it + : nullptr; +} +ITracker &TrackerBase::parent() +{ + assert(m_parent); // Should always be non-null except for root + return *m_parent; +} + +void TrackerBase::openChild() +{ + if (m_runState != ExecutingChildren) + { + m_runState = ExecutingChildren; + if (m_parent) + m_parent->openChild(); + } +} + +bool TrackerBase::isSectionTracker() const { return false; } +bool TrackerBase::isIndexTracker() const { return false; } + +void TrackerBase::open() +{ + m_runState = Executing; + moveToThis(); + if (m_parent) + m_parent->openChild(); +} + +void TrackerBase::close() +{ + + // Close any still open children (e.g. generators) + while (&m_ctx.currentTracker() != this) + m_ctx.currentTracker().close(); + + switch (m_runState) + { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if (m_children.empty() || m_children.back()->isComplete()) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR("Illogical state: " << m_runState); + + default: + CATCH_INTERNAL_ERROR("Unknown state: " << m_runState); + } + moveToParent(); + m_ctx.completeCycle(); +} +void TrackerBase::fail() +{ + m_runState = Failed; + if (m_parent) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); +} +void TrackerBase::markAsNeedingAnotherRun() +{ + m_runState = NeedsAnotherRun; +} + +void TrackerBase::moveToParent() +{ + assert(m_parent); + m_ctx.setCurrentTracker(m_parent); +} +void TrackerBase::moveToThis() +{ + m_ctx.setCurrentTracker(this); +} + +SectionTracker::SectionTracker(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent) + : TrackerBase(nameAndLocation, ctx, parent) +{ + if (parent) + { + while (!parent->isSectionTracker()) + parent = &parent->parent(); + + SectionTracker &parentSection = static_cast(*parent); + addNextFilters(parentSection.m_filters); + } +} + +bool SectionTracker::isSectionTracker() const { return true; } + +SectionTracker &SectionTracker::acquire(TrackerContext &ctx, NameAndLocation const &nameAndLocation) +{ + std::shared_ptr section; + + ITracker ¤tTracker = ctx.currentTracker(); + if (ITrackerPtr childTracker = currentTracker.findChild(nameAndLocation)) + { + assert(childTracker); + assert(childTracker->isSectionTracker()); + section = std::static_pointer_cast(childTracker); + } + else + { + section = std::make_shared(nameAndLocation, ctx, ¤tTracker); + currentTracker.addChild(section); + } + if (!ctx.completedCycle()) + section->tryOpen(); + return *section; +} + +void SectionTracker::tryOpen() +{ + if (!isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name)) + open(); +} + +void SectionTracker::addInitialFilters(std::vector const &filters) +{ + if (!filters.empty()) + { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert(m_filters.end(), filters.begin(), filters.end()); + } +} +void SectionTracker::addNextFilters(std::vector const &filters) +{ + if (filters.size() > 1) + m_filters.insert(m_filters.end(), ++filters.begin(), filters.end()); +} + +IndexTracker::IndexTracker(NameAndLocation const &nameAndLocation, TrackerContext &ctx, ITracker *parent, int size) + : TrackerBase(nameAndLocation, ctx, parent), + m_size(size) +{ +} + +bool IndexTracker::isIndexTracker() const { return true; } + +IndexTracker &IndexTracker::acquire(TrackerContext &ctx, NameAndLocation const &nameAndLocation, int size) +{ + std::shared_ptr tracker; + + ITracker ¤tTracker = ctx.currentTracker(); + if (ITrackerPtr childTracker = currentTracker.findChild(nameAndLocation)) + { + assert(childTracker); + assert(childTracker->isIndexTracker()); + tracker = std::static_pointer_cast(childTracker); + } + else + { + tracker = std::make_shared(nameAndLocation, ctx, ¤tTracker, size); + currentTracker.addChild(tracker); + } + + if (!ctx.completedCycle() && !tracker->isComplete()) + { + if (tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; +} + +int IndexTracker::index() const { return m_index; } + +void IndexTracker::moveNext() +{ + m_index++; + m_children.clear(); +} + +void IndexTracker::close() +{ + TrackerBase::close(); + if (m_runState == CompletedSuccessfully && m_index < m_size - 1) + m_runState = Executing; +} + +} // namespace TestCaseTracking + +using TestCaseTracking::IndexTracker; +using TestCaseTracking::ITracker; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::TrackerContext; + +} // namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch +{ + +auto makeTestInvoker(void (*testAsFunction)()) noexcept -> ITestInvoker * +{ + return new (std::nothrow) TestInvokerAsFunction(testAsFunction); +} + +NameAndTags::NameAndTags(StringRef const &name_, StringRef const &tags_) noexcept : name(name_), tags(tags_) {} + +AutoReg::AutoReg(ITestInvoker *invoker, SourceLineInfo const &lineInfo, StringRef const &classOrMethod, NameAndTags const &nameAndTags) noexcept +{ + CATCH_TRY + { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName(classOrMethod), + nameAndTags, + lineInfo)); + } + CATCH_CATCH_ALL + { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } +} + +AutoReg::~AutoReg() = default; +} // namespace Catch +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include +#include +#include +#include + +namespace Catch +{ + +TestSpec::Pattern::~Pattern() = default; +TestSpec::NamePattern::~NamePattern() = default; +TestSpec::TagPattern::~TagPattern() = default; +TestSpec::ExcludedPattern::~ExcludedPattern() = default; + +TestSpec::NamePattern::NamePattern(std::string const &name) + : m_wildcardPattern(toLower(name), CaseSensitive::No) +{ +} +bool TestSpec::NamePattern::matches(TestCaseInfo const &testCase) const +{ + return m_wildcardPattern.matches(toLower(testCase.name)); +} + +TestSpec::TagPattern::TagPattern(std::string const &tag) : m_tag(toLower(tag)) {} +bool TestSpec::TagPattern::matches(TestCaseInfo const &testCase) const +{ + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); +} + +TestSpec::ExcludedPattern::ExcludedPattern(PatternPtr const &underlyingPattern) : m_underlyingPattern(underlyingPattern) {} +bool TestSpec::ExcludedPattern::matches(TestCaseInfo const &testCase) const { return !m_underlyingPattern->matches(testCase); } + +bool TestSpec::Filter::matches(TestCaseInfo const &testCase) const +{ + // All patterns in a filter must match for the filter to be a match + for (auto const &pattern : m_patterns) + { + if (!pattern->matches(testCase)) + return false; + } + return true; +} + +bool TestSpec::hasFilters() const +{ + return !m_filters.empty(); +} +bool TestSpec::matches(TestCaseInfo const &testCase) const +{ + // A TestSpec matches if any filter matches + for (auto const &filter : m_filters) + if (filter.matches(testCase)) + return true; + return false; +} +} // namespace Catch +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch +{ + +TestSpecParser::TestSpecParser(ITagAliasRegistry const &tagAliases) : m_tagAliases(&tagAliases) {} + +TestSpecParser &TestSpecParser::parse(std::string const &arg) +{ + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases(arg); + m_escapeChars.clear(); + for (m_pos = 0; m_pos < m_arg.size(); ++m_pos) + visitChar(m_arg[m_pos]); + if (m_mode == Name) + addPattern(); + return *this; +} +TestSpec TestSpecParser::testSpec() +{ + addFilter(); + return m_testSpec; +} + +void TestSpecParser::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); + case '\\': + return escape(); + 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 (c == '\\') + escape(); + } + else if (m_mode == EscapedName) + m_mode = Name; + else if (m_mode == QuotedName && c == '"') + addPattern(); + else if (m_mode == Tag && c == ']') + addPattern(); +} +void TestSpecParser::startNewMode(Mode mode, std::size_t start) +{ + m_mode = mode; + m_start = start; +} +void TestSpecParser::escape() +{ + if (m_mode == None) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back(m_pos); +} +std::string TestSpecParser::subString() const { return m_arg.substr(m_start, m_pos - m_start); } + +void TestSpecParser::addFilter() +{ + if (!m_currentFilter.m_patterns.empty()) + { + m_testSpec.m_filters.push_back(m_currentFilter); + m_currentFilter = TestSpec::Filter(); + } +} + +TestSpec parseTestSpec(std::string const &arg) +{ + return TestSpecParser(ITagAliasRegistry::get()).parse(arg).testSpec(); +} + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include + +static const uint64_t nanosecondsInSecond = 1000000000; + +namespace Catch +{ + +auto getCurrentNanosecondsSinceEpoch() -> uint64_t +{ + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); +} + +namespace +{ +auto estimateClockResolution() -> uint64_t +{ + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for (std::size_t i = 0; i < iterations; ++i) + { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do + { + ticks = getCurrentNanosecondsSinceEpoch(); + } while (ticks == baseTicks); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) + { + return sum / i; + } + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum / iterations; +} +} // namespace +auto getEstimatedClockResolution() -> uint64_t +{ + static auto s_resolution = estimateClockResolution(); + return s_resolution; +} + +void Timer::start() +{ + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); +} +auto Timer::getElapsedNanoseconds() const -> uint64_t +{ + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; +} +auto Timer::getElapsedMicroseconds() const -> uint64_t +{ + return getElapsedNanoseconds() / 1000; +} +auto Timer::getElapsedMilliseconds() const -> unsigned int +{ + return static_cast(getElapsedMicroseconds() / 1000); +} +auto Timer::getElapsedSeconds() const -> double +{ + return getElapsedMicroseconds() / 1000000.0; +} + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include +#include + +namespace Catch +{ + +namespace Detail +{ + +const std::string unprintableString = "{?}"; + +namespace +{ +const int hexThreshold = 255; + +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; + } +}; +} // namespace + +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); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for (; i != end; i += inc) + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); +} +} // namespace Detail + +template +std::string fpToString(T value, int precision) +{ + if (std::isnan(value)) + { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision(precision) + << std::fixed + << value; + std::string d = rss.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; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker::convert(const std::string &str) +{ + if (!getCurrentContext().getConfig()->showInvisibles()) + { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) + { + switch (c) + { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::string_view str) +{ + return ::Catch::Detail::stringify(std::string{str}); +} +#endif + +std::string StringMaker::convert(char const *str) +{ + if (str) + { + return ::Catch::Detail::stringify(std::string{str}); + } + else + { + return {"{null string}"}; + } +} +std::string StringMaker::convert(char *str) +{ + if (str) + { + return ::Catch::Detail::stringify(std::string{str}); + } + else + { + return {"{null string}"}; + } +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(const std::wstring &wstr) +{ + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) + { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::wstring_view str) +{ + return StringMaker::convert(std::wstring(str)); +} +#endif + +std::string StringMaker::convert(wchar_t const *str) +{ + if (str) + { + return ::Catch::Detail::stringify(std::wstring{str}); + } + else + { + return {"{null string}"}; + } +} +std::string StringMaker::convert(wchar_t *str) +{ + if (str) + { + return ::Catch::Detail::stringify(std::wstring{str}); + } + else + { + return {"{null string}"}; + } +} +#endif + +std::string StringMaker::convert(int value) +{ + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long value) +{ + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long long value) +{ + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) + { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(unsigned int value) +{ + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long value) +{ + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long long value) +{ + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) + { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(bool b) +{ + return b ? "true" : "false"; +} + +std::string StringMaker::convert(char value) +{ + if (value == '\r') + { + return "'\\r'"; + } + else if (value == '\f') + { + return "'\\f'"; + } + else if (value == '\n') + { + return "'\\n'"; + } + else if (value == '\t') + { + return "'\\t'"; + } + else if ('\0' <= value && value < ' ') + { + return ::Catch::Detail::stringify(static_cast(value)); + } + else + { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker::convert(signed char c) +{ + return ::Catch::Detail::stringify(static_cast(c)); +} +std::string StringMaker::convert(unsigned char c) +{ + return ::Catch::Detail::stringify(static_cast(c)); +} + +std::string StringMaker::convert(std::nullptr_t) +{ + return "nullptr"; +} + +std::string StringMaker::convert(float value) +{ + return fpToString(value, 5) + 'f'; +} +std::string StringMaker::convert(double value) +{ + return fpToString(value, 10); +} + +std::string ratio_string::symbol() { return "a"; } +std::string ratio_string::symbol() { return "f"; } +std::string ratio_string::symbol() { return "p"; } +std::string ratio_string::symbol() { return "n"; } +std::string ratio_string::symbol() { return "u"; } +std::string ratio_string::symbol() { return "m"; } + +} // end namespace Catch + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch +{ + +Counts 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 &Counts::operator+=(Counts const &other) +{ + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; +} + +std::size_t Counts::total() const +{ + return passed + failed + failedButOk; +} +bool Counts::allPassed() const +{ + return failed == 0 && failedButOk == 0; +} +bool Counts::allOk() const +{ + return failed == 0; +} + +Totals Totals::operator-(Totals const &other) const +{ + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; +} + +Totals &Totals::operator+=(Totals const &other) +{ + assertions += other.assertions; + testCases += other.testCases; + return *this; +} + +Totals 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; +} + +} // namespace Catch +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include + +namespace Catch +{ +bool uncaught_exceptions() +{ +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif +} +} // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp + +#include + +namespace Catch +{ + +Version::Version(unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const *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; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) + { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; +} + +Version const &libraryVersion() +{ + static Version version(2, 4, 1, "", 0); + return version; +} + +} // namespace Catch +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +#include + +namespace Catch +{ + +WildcardPattern::WildcardPattern(std::string const &pattern, + CaseSensitive::Choice caseSensitivity) + : m_caseSensitivity(caseSensitivity), + 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); + } +} + +bool WildcardPattern::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); + default: + CATCH_INTERNAL_ERROR("Unknown enum"); + } +} + +std::string WildcardPattern::adjustCase(std::string const &str) const +{ + return m_caseSensitivity == CaseSensitive::No ? toLower(str) : str; +} +} // namespace Catch +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +#include + +using uchar = unsigned char; + +namespace Catch +{ + +namespace +{ + +size_t trailingBytes(unsigned char c) +{ + if ((c & 0xE0) == 0xC0) + { + return 2; + } + if ((c & 0xF0) == 0xE0) + { + return 3; + } + if ((c & 0xF8) == 0xF0) + { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); +} + +uint32_t headerValue(unsigned char c) +{ + if ((c & 0xE0) == 0xC0) + { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) + { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) + { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); +} + +void hexEscapeChar(std::ostream &os, unsigned char c) +{ + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); +} + +} // anonymous namespace + +XmlEncode::XmlEncode(std::string const &str, ForWhat forWhat) + : m_str(str), + m_forWhat(forWhat) +{ +} + +void XmlEncode::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 idx = 0; idx < m_str.size(); ++idx) + { + uchar c = m_str[idx]; + switch (c) + { + case '<': + os << "<"; + break; + case '&': + os << "&"; + break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) + { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) + { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) + { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) + { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) + { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000)) + { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) + { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } +} + +std::ostream &operator<<(std::ostream &os, XmlEncode const &xmlEncode) +{ + xmlEncode.encodeTo(os); + return os; +} + +XmlWriter::ScopedElement::ScopedElement(XmlWriter *writer) + : m_writer(writer) +{ +} + +XmlWriter::ScopedElement::ScopedElement(ScopedElement &&other) noexcept + : m_writer(other.m_writer) +{ + other.m_writer = nullptr; +} +XmlWriter::ScopedElement &XmlWriter::ScopedElement::operator=(ScopedElement &&other) noexcept +{ + if (m_writer) + { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; +} + +XmlWriter::ScopedElement::~ScopedElement() +{ + if (m_writer) + m_writer->endElement(); +} + +XmlWriter::ScopedElement &XmlWriter::ScopedElement::writeText(std::string const &text, bool indent) +{ + m_writer->writeText(text, indent); + return *this; +} + +XmlWriter::XmlWriter(std::ostream &os) : m_os(os) +{ + writeDeclaration(); +} + +XmlWriter::~XmlWriter() +{ + while (!m_tags.empty()) + endElement(); +} + +XmlWriter &XmlWriter::startElement(std::string const &name) +{ + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back(name); + m_indent += " "; + m_tagIsOpen = true; + return *this; +} + +XmlWriter::ScopedElement XmlWriter::scopedElement(std::string const &name) +{ + ScopedElement scoped(this); + startElement(name); + return scoped; +} + +XmlWriter &XmlWriter::endElement() +{ + newlineIfNecessary(); + m_indent = m_indent.substr(0, m_indent.size() - 2); + if (m_tagIsOpen) + { + m_os << "/>"; + m_tagIsOpen = false; + } + else + { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; +} + +XmlWriter &XmlWriter::writeAttribute(std::string const &name, std::string const &attribute) +{ + if (!name.empty() && !attribute.empty()) + m_os << ' ' << name << "=\"" << XmlEncode(attribute, XmlEncode::ForAttributes) << '"'; + return *this; +} + +XmlWriter &XmlWriter::writeAttribute(std::string const &name, bool attribute) +{ + m_os << ' ' << name << "=\"" << (attribute ? "true" : "false") << '"'; + return *this; +} + +XmlWriter &XmlWriter::writeText(std::string const &text, bool indent) +{ + if (!text.empty()) + { + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if (tagWasOpen && indent) + m_os << m_indent; + m_os << XmlEncode(text); + m_needsNewline = true; + } + return *this; +} + +XmlWriter &XmlWriter::writeComment(std::string const &text) +{ + ensureTagClosed(); + m_os << m_indent << ""; + m_needsNewline = true; + return *this; +} + +void XmlWriter::writeStylesheetRef(std::string const &url) +{ + m_os << "\n"; +} + +XmlWriter &XmlWriter::writeBlankLine() +{ + ensureTagClosed(); + m_os << '\n'; + return *this; +} + +void XmlWriter::ensureTagClosed() +{ + if (m_tagIsOpen) + { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } +} + +void XmlWriter::writeDeclaration() +{ + m_os << "\n"; +} + +void XmlWriter::newlineIfNecessary() +{ + if (m_needsNewline) + { + m_os << std::endl; + m_needsNewline = false; + } +} +} // namespace Catch +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include +#include +#include +#include +#include + +namespace Catch +{ +void prepareExpandedExpression(AssertionResult &result) +{ + result.getExpandedExpression(); +} + +// Because formatting using c++ streams is stateful, drop down to C is required +// Alternatively we could use stringstream, but its performance is... not good. +std::string getFormattedDuration(double duration) +{ + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); +} + +TestEventListenerBase::TestEventListenerBase(ReporterConfig const &_config) + : StreamingReporterBase(_config) {} + +void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + +bool TestEventListenerBase::assertionEnded(AssertionStats const &) +{ + return false; +} + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace +{ + +#ifdef CATCH_PLATFORM_MAC +const char *failedString() +{ + return "FAILED"; +} +const char *passedString() { return "PASSED"; } +#else +const char *failedString() +{ + return "failed"; +} +const char *passedString() { return "passed"; } +#endif + +// Colour::LightGrey +Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + +std::string bothOrAll(std::size_t count) +{ + return count == 1 ? std::string() : count == 2 ? "both " : "all "; +} + +} // namespace + +namespace Catch +{ +namespace +{ +// 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. +void printTotals(std::ostream &out, const Totals &totals) +{ + if (totals.testCases.total() == 0) + { + out << "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) : std::string(); + out << "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) + { + out << "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } + else if (totals.assertions.failed) + { + Colour colour(Colour::ResultError); + out << "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " + << pluralise(totals.assertions.failed, "assertion") << '.'; + } + else + { + Colour colour(Colour::ResultSuccess); + out << "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter +{ +public: + AssertionPrinter &operator=(AssertionPrinter const &) = delete; + AssertionPrinter(AssertionPrinter const &) = delete; + AssertionPrinter(std::ostream &_stream, AssertionStats const &_stats, bool _printInfoMessages) + : stream(_stream), 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: + void printSourceInfo() const + { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const &passOrFail) const + { + if (!passOrFail.empty()) + { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const &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 (or auto) 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; + AssertionResult const &result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; +}; + +} // namespace + +std::string CompactReporter::getDescription() +{ + return "Reports test results on a single line, suitable for IDEs"; +} + +ReporterPreferences CompactReporter::getPreferences() const +{ + return m_reporterPrefs; +} + +void CompactReporter::noMatchingTestCases(std::string const &spec) +{ + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void CompactReporter::assertionStarting(AssertionInfo const &) {} + +bool CompactReporter::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; +} + +void CompactReporter::sectionEnded(SectionStats const &_sectionStats) +{ + if (m_config->showDurations() == ShowDurations::Always) + { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } +} + +void CompactReporter::testRunEnded(TestRunStats const &_testRunStats) +{ + printTotals(stream, _testRunStats.totals); + stream << '\n' + << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +CompactReporter::~CompactReporter() {} + +CATCH_REGISTER_REPORTER("compact", CompactReporter) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch \ + // Note that 4062 (not all labels are handled \ + // and default is missing) is enabled +#endif + +namespace Catch +{ + +namespace +{ + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter +{ +public: + ConsoleAssertionPrinter &operator=(ConsoleAssertionPrinter const &) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const &) = delete; + ConsoleAssertionPrinter(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 "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + 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 << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const + { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const &msg : messages) + { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(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; +}; + +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; +} + +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; +} + +struct ColumnInfo +{ + enum Justification + { + Left, + Right + }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak +{ +}; +struct RowBreak +{ +}; + +class Duration +{ + enum class Unit + { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + uint64_t m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) + { + if (m_units == Unit::Auto) + { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + } + + auto value() const -> double + { + switch (m_units) + { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return static_cast(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string + { + switch (m_units) + { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + } + friend auto operator<<(std::ostream &os, Duration const &duration) -> std::ostream & + { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // namespace + +class TablePrinter +{ + std::ostream &m_os; + std::vector m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter(std::ostream &os, std::vector columnInfos) + : m_os(os), + m_columnInfos(std::move(columnInfos)) {} + + auto columnInfos() const -> std::vector const & + { + return m_columnInfos; + } + + void open() + { + if (!m_isOpen) + { + m_isOpen = true; + *this << RowBreak(); + for (auto const &info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() + { + if (m_isOpen) + { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template + friend TablePrinter &operator<<(TablePrinter &tp, T const &value) + { + tp.m_oss << value; + return tp; + } + + friend TablePrinter &operator<<(TablePrinter &tp, ColumnBreak) + { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) + { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter &operator<<(TablePrinter &tp, RowBreak) + { + if (tp.m_currentColumn > 0) + { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const &config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + {{"benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left}, + {"iters", 8, ColumnInfo::Right}, + {"elapsed ns", 14, ColumnInfo::Right}, + {"average", 14, ColumnInfo::Right}})) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() +{ + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const &spec) +{ + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const &) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const &_assertionStats) +{ + AssertionResult const &result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const &_sectionInfo) +{ + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const &_sectionStats) +{ + m_tablePrinter->close(); + 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_config->showDurations() == ShowDurations::Always) + { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) + { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const &info) +{ + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column(info.name).width(static_cast(m_tablePrinter->columnInfos()[0].width - 2)); + + bool firstLine = true; + for (auto line : nameCol) + { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const &stats) +{ + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const &_testCaseStats) +{ + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::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); +} +void ConsoleReporter::testRunEnded(TestRunStats const &_testRunStats) +{ + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +void ConsoleReporter::lazyPrint() +{ + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() +{ + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) + { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::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 ConsoleReporter::lazyPrintGroupInfo() +{ + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) + { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() +{ + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) + { + Colour colourGuard(Colour::Headers); + + auto + 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.back().lineInfo; + + if (!lineInfo.empty()) + { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' + << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const &_name) +{ + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::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 ConsoleReporter::printHeaderString(std::string const &_string, std::size_t indent) +{ + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn +{ + + SummaryColumn(std::string _label, Colour::Code _colour) + : label(std::move(_label)), + colour(_colour) {} + SummaryColumn addRow(std::size_t count) + { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto &oldRow : rows) + { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; +}; + +void ConsoleReporter::printTotals(Totals const &totals) +{ + if (totals.testCases.total() == 0) + { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } + else if (totals.assertions.total() > 0 && totals.testCases.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 ConsoleReporter::printSummaryRow(std::string const &label, std::vector const &cols, std::size_t row) +{ + for (auto col : cols) + { + std::string value = col.rows[row]; + if (col.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(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::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 ConsoleReporter::printSummaryDivider() +{ + stream << getLineOfChars<'-'>() << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include +#include +#include +#include + +namespace Catch +{ + +namespace +{ +std::string getCurrentTimestamp() +{ + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm *timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + 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); +} + +std::string fileNameTag(const std::vector &tags) +{ + auto it = std::find_if(begin(tags), + end(tags), + [](std::string const &tag) { return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); +} +} // anonymous namespace + +JunitReporter::JunitReporter(ReporterConfig const &_config) + : CumulativeReporterBase(_config), + xml(_config.stream()) +{ + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; +} + +JunitReporter::~JunitReporter() {} + +std::string JunitReporter::getDescription() +{ + return "Reports test results in an XML format that looks like Ant's junitreport target"; +} + +void JunitReporter::noMatchingTestCases(std::string const & /*spec*/) {} + +void JunitReporter::testRunStarting(TestRunInfo const &runInfo) +{ + CumulativeReporterBase::testRunStarting(runInfo); + xml.startElement("testsuites"); +} + +void JunitReporter::testGroupStarting(GroupInfo const &groupInfo) +{ + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting(groupInfo); +} + +void JunitReporter::testCaseStarting(TestCaseInfo const &testCaseInfo) +{ + m_okToFail = testCaseInfo.okToFail(); +} + +bool JunitReporter::assertionEnded(AssertionStats const &assertionStats) +{ + if (assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded(assertionStats); +} + +void JunitReporter::testCaseEnded(TestCaseStats const &testCaseStats) +{ + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded(testCaseStats); +} + +void JunitReporter::testGroupEnded(TestGroupStats const &testGroupStats) +{ + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded(testGroupStats); + writeGroup(*m_testGroups.back(), suiteTime); +} + +void JunitReporter::testRunEndedCumulative() +{ + xml.endElement(); +} + +void JunitReporter::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", getCurrentTimestamp()); + + // Write test cases + for (auto const &child : groupNode.children) + writeTestCase(*child); + + xml.scopedElement("system-out").writeText(trim(stdOutForSuite), false); + xml.scopedElement("system-err").writeText(trim(stdErrForSuite), false); +} + +void JunitReporter::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()) + { + className = fileNameTag(stats.testInfo.tags); + if (className.empty()) + className = "global"; + } + + if (!m_config->name().empty()) + className = m_config->name() + "." + className; + + writeSection(className, "", rootSection); +} + +void JunitReporter::writeSection(std::string const &className, + std::string const &rootName, + SectionNode const §ionNode) +{ + 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::Detail::stringify(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 (auto const &childNode : sectionNode.childSections) + if (className.empty()) + writeSection(name, "", *childNode); + else + writeSection(className, name, *childNode); +} + +void JunitReporter::writeAssertions(SectionNode const §ionNode) +{ + for (auto const &assertion : sectionNode.assertions) + writeAssertion(assertion); +} + +void JunitReporter::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()); + + ReusableStringStream rss; + if (!result.getMessage().empty()) + rss << result.getMessage() << '\n'; + for (auto const &msg : stats.infoMessages) + if (msg.type == ResultWas::Info) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText(rss.str(), false); + } +} + +CATCH_REGISTER_REPORTER("junit", JunitReporter) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp + +#include + +namespace Catch +{ + +ListeningReporter::ListeningReporter() +{ + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; +} + +void ListeningReporter::addListener(IStreamingReporterPtr &&listener) +{ + m_listeners.push_back(std::move(listener)); +} + +void ListeningReporter::addReporter(IStreamingReporterPtr &&reporter) +{ + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move(reporter); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; +} + +ReporterPreferences ListeningReporter::getPreferences() const +{ + return m_preferences; +} + +std::set ListeningReporter::getSupportedVerbosities() +{ + return std::set{}; +} + +void ListeningReporter::noMatchingTestCases(std::string const &spec) +{ + for (auto const &listener : m_listeners) + { + listener->noMatchingTestCases(spec); + } + m_reporter->noMatchingTestCases(spec); +} + +void ListeningReporter::benchmarkStarting(BenchmarkInfo const &benchmarkInfo) +{ + for (auto const &listener : m_listeners) + { + listener->benchmarkStarting(benchmarkInfo); + } + m_reporter->benchmarkStarting(benchmarkInfo); +} +void ListeningReporter::benchmarkEnded(BenchmarkStats const &benchmarkStats) +{ + for (auto const &listener : m_listeners) + { + listener->benchmarkEnded(benchmarkStats); + } + m_reporter->benchmarkEnded(benchmarkStats); +} + +void ListeningReporter::testRunStarting(TestRunInfo const &testRunInfo) +{ + for (auto const &listener : m_listeners) + { + listener->testRunStarting(testRunInfo); + } + m_reporter->testRunStarting(testRunInfo); +} + +void ListeningReporter::testGroupStarting(GroupInfo const &groupInfo) +{ + for (auto const &listener : m_listeners) + { + listener->testGroupStarting(groupInfo); + } + m_reporter->testGroupStarting(groupInfo); +} + +void ListeningReporter::testCaseStarting(TestCaseInfo const &testInfo) +{ + for (auto const &listener : m_listeners) + { + listener->testCaseStarting(testInfo); + } + m_reporter->testCaseStarting(testInfo); +} + +void ListeningReporter::sectionStarting(SectionInfo const §ionInfo) +{ + for (auto const &listener : m_listeners) + { + listener->sectionStarting(sectionInfo); + } + m_reporter->sectionStarting(sectionInfo); +} + +void ListeningReporter::assertionStarting(AssertionInfo const &assertionInfo) +{ + for (auto const &listener : m_listeners) + { + listener->assertionStarting(assertionInfo); + } + m_reporter->assertionStarting(assertionInfo); +} + +// The return value indicates if the messages buffer should be cleared: +bool ListeningReporter::assertionEnded(AssertionStats const &assertionStats) +{ + for (auto const &listener : m_listeners) + { + static_cast(listener->assertionEnded(assertionStats)); + } + return m_reporter->assertionEnded(assertionStats); +} + +void ListeningReporter::sectionEnded(SectionStats const §ionStats) +{ + for (auto const &listener : m_listeners) + { + listener->sectionEnded(sectionStats); + } + m_reporter->sectionEnded(sectionStats); +} + +void ListeningReporter::testCaseEnded(TestCaseStats const &testCaseStats) +{ + for (auto const &listener : m_listeners) + { + listener->testCaseEnded(testCaseStats); + } + m_reporter->testCaseEnded(testCaseStats); +} + +void ListeningReporter::testGroupEnded(TestGroupStats const &testGroupStats) +{ + for (auto const &listener : m_listeners) + { + listener->testGroupEnded(testGroupStats); + } + m_reporter->testGroupEnded(testGroupStats); +} + +void ListeningReporter::testRunEnded(TestRunStats const &testRunStats) +{ + for (auto const &listener : m_listeners) + { + listener->testRunEnded(testRunStats); + } + m_reporter->testRunEnded(testRunStats); +} + +void ListeningReporter::skipTest(TestCaseInfo const &testInfo) +{ + for (auto const &listener : m_listeners) + { + listener->skipTest(testInfo); + } + m_reporter->skipTest(testInfo); +} + +bool ListeningReporter::isMulti() const +{ + return true; +} + +} // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4061) // Not all labels are EXPLICITLY handled in switch \ + // Note that 4062 (not all labels are handled \ + // and default is missing) is enabled +#endif + +namespace Catch +{ +XmlReporter::XmlReporter(ReporterConfig const &_config) + : StreamingReporterBase(_config), + m_xml(_config.stream()) +{ + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; +} + +XmlReporter::~XmlReporter() = default; + +std::string XmlReporter::getDescription() +{ + return "Reports test results as an XML document"; +} + +std::string XmlReporter::getStylesheetRef() const +{ + return std::string(); +} + +void XmlReporter::writeSourceInfo(SourceLineInfo const &sourceInfo) +{ + m_xml + .writeAttribute("filename", sourceInfo.file) + .writeAttribute("line", sourceInfo.line); +} + +void XmlReporter::noMatchingTestCases(std::string const &s) +{ + StreamingReporterBase::noMatchingTestCases(s); +} + +void XmlReporter::testRunStarting(TestRunInfo const &testInfo) +{ + StreamingReporterBase::testRunStarting(testInfo); + std::string stylesheetRef = getStylesheetRef(); + if (!stylesheetRef.empty()) + m_xml.writeStylesheetRef(stylesheetRef); + m_xml.startElement("Catch"); + if (!m_config->name().empty()) + m_xml.writeAttribute("name", m_config->name()); +} + +void XmlReporter::testGroupStarting(GroupInfo const &groupInfo) +{ + StreamingReporterBase::testGroupStarting(groupInfo); + m_xml.startElement("Group") + .writeAttribute("name", groupInfo.name); +} + +void XmlReporter::testCaseStarting(TestCaseInfo const &testInfo) +{ + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement("TestCase") + .writeAttribute("name", trim(testInfo.name)) + .writeAttribute("description", testInfo.description) + .writeAttribute("tags", testInfo.tagsAsString()); + + writeSourceInfo(testInfo.lineInfo); + + if (m_config->showDurations() == ShowDurations::Always) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); +} + +void XmlReporter::sectionStarting(SectionInfo const §ionInfo) +{ + StreamingReporterBase::sectionStarting(sectionInfo); + if (m_sectionDepth++ > 0) + { + m_xml.startElement("Section") + .writeAttribute("name", trim(sectionInfo.name)); + writeSourceInfo(sectionInfo.lineInfo); + m_xml.ensureTagClosed(); + } +} + +void XmlReporter::assertionStarting(AssertionInfo const &) {} + +bool XmlReporter::assertionEnded(AssertionStats const &assertionStats) +{ + + AssertionResult const &result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if (includeResults || result.getResultType() == ResultWas::Warning) + { + // Print any info messages in tags. + for (auto const &msg : assertionStats.infoMessages) + { + if (msg.type == ResultWas::Info && includeResults) + { + m_xml.scopedElement("Info") + .writeText(msg.message); + } + else if (msg.type == ResultWas::Warning) + { + m_xml.scopedElement("Warning") + .writeText(msg.message); + } + } + } + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return true; + + // Print the expression if there is one. + if (result.hasExpression()) + { + m_xml.startElement("Expression") + .writeAttribute("success", result.succeeded()) + .writeAttribute("type", result.getTestMacroName()); + + writeSourceInfo(result.getSourceInfo()); + + m_xml.scopedElement("Original") + .writeText(result.getExpression()); + m_xml.scopedElement("Expanded") + .writeText(result.getExpandedExpression()); + } + + // And... Print a result applicable to each result type. + switch (result.getResultType()) + { + case ResultWas::ThrewException: + m_xml.startElement("Exception"); + writeSourceInfo(result.getSourceInfo()); + m_xml.writeText(result.getMessage()); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement("FatalErrorCondition"); + writeSourceInfo(result.getSourceInfo()); + m_xml.writeText(result.getMessage()); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement("Info") + .writeText(result.getMessage()); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement("Failure"); + writeSourceInfo(result.getSourceInfo()); + m_xml.writeText(result.getMessage()); + m_xml.endElement(); + break; + default: + break; + } + + if (result.hasExpression()) + m_xml.endElement(); + + return true; +} + +void XmlReporter::sectionEnded(SectionStats const §ionStats) +{ + 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(); + } +} + +void XmlReporter::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()); + + if (!testCaseStats.stdOut.empty()) + m_xml.scopedElement("StdOut").writeText(trim(testCaseStats.stdOut), false); + if (!testCaseStats.stdErr.empty()) + m_xml.scopedElement("StdErr").writeText(trim(testCaseStats.stdErr), false); + + m_xml.endElement(); +} + +void XmlReporter::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(); +} + +void XmlReporter::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(); +} + +CATCH_REGISTER_REPORTER("xml", XmlReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch +{ +LeakDetector leakDetector; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_impl.hpp +#endif + +#ifdef CATCH_CONFIG_MAIN +// start catch_default_main.hpp + +#ifndef __OBJC__ + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain(int argc, wchar_t *argv[], wchar_t *[]) +{ +#else +// Standard C/C++ main entry point +int main(int argc, char *argv[]) +{ +#endif + + 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 **)argv); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +// end catch_default_main.hpp +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +#undef CLARA_CONFIG_MAIN +#endif + +#if !defined(CATCH_CONFIG_DISABLE) +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE(...) INTERNAL_CATCH_TEST("CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__) +#define CATCH_REQUIRE_FALSE(...) INTERNAL_CATCH_TEST("CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__) + +#define CATCH_REQUIRE_THROWS(...) INTERNAL_CATCH_THROWS("CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__) +#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr) +#define CATCH_REQUIRE_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW(...) INTERNAL_CATCH_NO_THROW("CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__) + +#define CATCH_CHECK(...) INTERNAL_CATCH_TEST("CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_CHECK_FALSE(...) INTERNAL_CATCH_TEST("CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__) +#define CATCH_CHECKED_IF(...) INTERNAL_CATCH_IF("CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_CHECKED_ELSE(...) INTERNAL_CATCH_ELSE("CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_CHECK_NOFAIL(...) INTERNAL_CATCH_TEST("CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) + +#define CATCH_CHECK_THROWS(...) INTERNAL_CATCH_THROWS("CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__) +#define CATCH_CHECK_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr) +#define CATCH_CHECK_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW(...) INTERNAL_CATCH_NO_THROW("CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT(arg, matcher) INTERNAL_CHECK_THAT("CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg) + +#define CATCH_REQUIRE_THAT(arg, matcher) INTERNAL_CHECK_THAT("CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO(msg) INTERNAL_CATCH_INFO("CATCH_INFO", msg) +#define CATCH_WARN(msg) INTERNAL_CATCH_MSG("CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg) +#define CATCH_CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__) + +#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_REGISTER_TEST_CASE(Function, ...) INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__) +#define CATCH_SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__) +#define CATCH_DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__) +#define CATCH_FAIL(...) INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__) +#define CATCH_FAIL_CHECK(...) INTERNAL_CATCH_MSG("CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CATCH_SUCCEED(...) INTERNAL_CATCH_MSG("CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO(...) CATCH_TEST_CASE("Scenario: " __VA_ARGS__) +#define CATCH_SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, "Scenario: " __VA_ARGS__) +#define CATCH_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Given: " << desc) +#define CATCH_AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION("And given: " << desc) +#define CATCH_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" When: " << desc) +#define CATCH_AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And when: " << desc) +#define CATCH_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Then: " << desc) +#define CATCH_AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And: " << desc) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE(...) INTERNAL_CATCH_TEST("REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__) +#define REQUIRE_FALSE(...) INTERNAL_CATCH_TEST("REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__) + +#define REQUIRE_THROWS(...) INTERNAL_CATCH_THROWS("REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr) +#define REQUIRE_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW(...) INTERNAL_CATCH_NO_THROW("REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__) + +#define CHECK(...) INTERNAL_CATCH_TEST("CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CHECK_FALSE(...) INTERNAL_CATCH_TEST("CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__) +#define CHECKED_IF(...) INTERNAL_CATCH_IF("CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CHECKED_ELSE(...) INTERNAL_CATCH_ELSE("CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CHECK_NOFAIL(...) INTERNAL_CATCH_TEST("CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__) + +#define CHECK_THROWS(...) INTERNAL_CATCH_THROWS("CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define CHECK_THROWS_AS(expr, exceptionType) INTERNAL_CATCH_THROWS_AS("CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr) +#define CHECK_THROWS_WITH(expr, matcher) INTERNAL_CATCH_THROWS_STR_MATCHES("CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher) INTERNAL_CATCH_THROWS_MATCHES("CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW(...) INTERNAL_CATCH_NO_THROW("CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT(arg, matcher) INTERNAL_CHECK_THAT("CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg) + +#define REQUIRE_THAT(arg, matcher) INTERNAL_CHECK_THAT("REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO(msg) INTERNAL_CATCH_INFO("INFO", msg) +#define WARN(msg) INTERNAL_CATCH_MSG("WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg) +#define CAPTURE(...) INTERNAL_CATCH_CAPTURE(INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__) + +#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 REGISTER_TEST_CASE(Function, ...) INTERNAL_CATCH_REGISTER_TESTCASE(Function, __VA_ARGS__) +#define SECTION(...) INTERNAL_CATCH_SECTION(__VA_ARGS__) +#define DYNAMIC_SECTION(...) INTERNAL_CATCH_DYNAMIC_SECTION(__VA_ARGS__) +#define FAIL(...) INTERNAL_CATCH_MSG("FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__) +#define FAIL_CHECK(...) INTERNAL_CATCH_MSG("FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define SUCCEED(...) INTERNAL_CATCH_MSG("SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#endif + +#define CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION(signature) + +// "BDD-style" convenience wrappers +#define SCENARIO(...) TEST_CASE("Scenario: " __VA_ARGS__) +#define SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TEST_CASE_METHOD(className, "Scenario: " __VA_ARGS__) + +#define GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Given: " << desc) +#define AND_GIVEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION("And given: " << desc) +#define WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" When: " << desc) +#define AND_WHEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And when: " << desc) +#define THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" Then: " << desc) +#define AND_THEN(desc) INTERNAL_CATCH_DYNAMIC_SECTION(" And: " << desc) + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE(...) (void)(0) +#define CATCH_REQUIRE_FALSE(...) (void)(0) + +#define CATCH_REQUIRE_THROWS(...) (void)(0) +#define CATCH_REQUIRE_THROWS_AS(expr, exceptionType) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH(expr, matcher) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW(...) (void)(0) + +#define CATCH_CHECK(...) (void)(0) +#define CATCH_CHECK_FALSE(...) (void)(0) +#define CATCH_CHECKED_IF(...) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE(...) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL(...) (void)(0) + +#define CATCH_CHECK_THROWS(...) (void)(0) +#define CATCH_CHECK_THROWS_AS(expr, exceptionType) (void)(0) +#define CATCH_CHECK_THROWS_WITH(expr, matcher) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW(...) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT(arg, matcher) (void)(0) + +#define CATCH_REQUIRE_THAT(arg, matcher) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO(msg) (void)(0) +#define CATCH_WARN(msg) (void)(0) +#define CATCH_CAPTURE(msg) (void)(0) + +#define CATCH_TEST_CASE(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) +#define CATCH_TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) +#define CATCH_METHOD_AS_TEST_CASE(method, ...) +#define CATCH_REGISTER_TEST_CASE(Function, ...) (void)(0) +#define CATCH_SECTION(...) +#define CATCH_DYNAMIC_SECTION(...) +#define CATCH_FAIL(...) (void)(0) +#define CATCH_FAIL_CHECK(...) (void)(0) +#define CATCH_SUCCEED(...) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) +#define CATCH_SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), className) +#define CATCH_GIVEN(desc) +#define CATCH_AND_GIVEN(desc) +#define CATCH_WHEN(desc) +#define CATCH_AND_WHEN(desc) +#define CATCH_THEN(desc) +#define CATCH_AND_THEN(desc) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE(...) (void)(0) +#define REQUIRE_FALSE(...) (void)(0) + +#define REQUIRE_THROWS(...) (void)(0) +#define REQUIRE_THROWS_AS(expr, exceptionType) (void)(0) +#define REQUIRE_THROWS_WITH(expr, matcher) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW(...) (void)(0) + +#define CHECK(...) (void)(0) +#define CHECK_FALSE(...) (void)(0) +#define CHECKED_IF(...) if (__VA_ARGS__) +#define CHECKED_ELSE(...) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL(...) (void)(0) + +#define CHECK_THROWS(...) (void)(0) +#define CHECK_THROWS_AS(expr, exceptionType) (void)(0) +#define CHECK_THROWS_WITH(expr, matcher) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES(expr, exceptionType, matcher) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW(...) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT(arg, matcher) (void)(0) + +#define REQUIRE_THAT(arg, matcher) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO(msg) (void)(0) +#define WARN(msg) (void)(0) +#define CAPTURE(msg) (void)(0) + +#define TEST_CASE(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) +#define TEST_CASE_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) +#define METHOD_AS_TEST_CASE(method, ...) +#define REGISTER_TEST_CASE(Function, ...) (void)(0) +#define SECTION(...) +#define DYNAMIC_SECTION(...) +#define FAIL(...) (void)(0) +#define FAIL_CHECK(...) (void)(0) +#define SUCCEED(...) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION(signature) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG(INTERNAL_CATCH_UNIQUE_NAME(catch_internal_ExceptionTranslator), signature) + +// "BDD-style" convenience wrappers +#define SCENARIO(...) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____)) +#define SCENARIO_METHOD(className, ...) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____T_E_S_T____), className) + +#define GIVEN(desc) +#define AND_GIVEN(desc) +#define WHEN(desc) +#define AND_WHEN(desc) +#define THEN(desc) +#define AND_THEN(desc) + +using Catch::Detail::Approx; + +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h + +#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 + +// end catch_reenable_warnings.h +// end catch.hpp +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..98c2408 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,10 @@ +#define CATCH_CONFIG_MAIN +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/test/test_actions.hpp b/test/test_actions.hpp new file mode 100644 index 0000000..357ad1c --- /dev/null +++ b/test/test_actions.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +TEST_CASE("Users can use defaul value inside actions", "[actions]") { + argparse::ArgumentParser program("test"); + program.add_argument("input") + .default_value("bar") + .action([=](const std::string& value) { + static const std::vector choices = { "foo", "bar", "baz" }; + if (std::find(choices.begin(), choices.end(), value) != choices.end()) { + return value; + } + return std::string{ "bar" }; + }); + + program.parse_args({ "test", "fez" }); + REQUIRE(program.get("input") == "bar"); +} \ No newline at end of file diff --git a/test/test_compound_arguments.hpp b/test/test_compound_arguments.hpp new file mode 100644 index 0000000..88bbb85 --- /dev/null +++ b/test/test_compound_arguments.hpp @@ -0,0 +1,152 @@ +#pragma once +#include +#include + +TEST_CASE("Parse compound toggle arguments with implicit values", "[compound_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-u") + .default_value(false) + .implicit_value(true); + + program.add_argument("-x") + .default_value(false) + .implicit_value(true); + + program.parse_args({ "./test.exe", "-aux" }); + REQUIRE(program.get("-a") == true); + REQUIRE(program.get("-u") == true); + REQUIRE(program.get("-x") == true); +} + +TEST_CASE("Parse compound toggle arguments with implicit values and nargs", "[compound_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-b") + .default_value(false) + .implicit_value(true); + + program.add_argument("-c") + .nargs(2) + .action([](const std::string& value) { return std::stof(value); }); + + program.add_argument("--input_files") + .nargs(3); + + program.parse_args({ "./test.exe", "-abc", "3.14", "2.718", "--input_files", + "a.txt", "b.txt", "c.txt" }); + REQUIRE(program.get("-a") == true); + REQUIRE(program.get("-b") == true); + auto c = program.get>("-c"); + REQUIRE(c.size() == 2); + REQUIRE(c[0] == 3.14f); + REQUIRE(c[1] == 2.718f); + auto input_files = program.get>("--input_files"); + REQUIRE(input_files.size() == 3); + REQUIRE(input_files[0] == "a.txt"); + REQUIRE(input_files[1] == "b.txt"); + REQUIRE(input_files[2] == "c.txt"); +} + +TEST_CASE("Parse compound toggle arguments with implicit values and nargs and other positional arguments", "[compound_arguments]") { + argparse::ArgumentParser program("test"); + + program.add_argument("numbers") + .nargs(3) + .action([](const std::string& value) { return std::stoi(value); }); + + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-b") + .default_value(false) + .implicit_value(true); + + program.add_argument("-c") + .nargs(2) + .action([](const std::string& value) { return std::stof(value); }); + + program.add_argument("--input_files") + .nargs(3); + + program.parse_args({ "./test.exe", "1", "-abc", "3.14", "2.718", "2", "--input_files", + "a.txt", "b.txt", "c.txt", "3" }); + + REQUIRE(program.get("-a") == true); + REQUIRE(program.get("-b") == true); + auto c = program.get>("-c"); + REQUIRE(c.size() == 2); + REQUIRE(c[0] == 3.14f); + REQUIRE(c[1] == 2.718f); + auto input_files = program.get>("--input_files"); + REQUIRE(input_files.size() == 3); + REQUIRE(input_files[0] == "a.txt"); + REQUIRE(input_files[1] == "b.txt"); + REQUIRE(input_files[2] == "c.txt"); + auto numbers = program.get>("numbers"); + REQUIRE(numbers.size() == 3); + REQUIRE(numbers[0] == 1); + REQUIRE(numbers[1] == 2); + REQUIRE(numbers[2] == 3); + auto numbers_list = program.get>("numbers"); + REQUIRE(numbers.size() == 3); + REQUIRE(argparse::get_from_list(numbers_list, 0) == 1); + REQUIRE(argparse::get_from_list(numbers_list, 1) == 2); + REQUIRE(argparse::get_from_list(numbers_list, 2) == 3); +} + +TEST_CASE("Parse out-of-order compound arguments", "[compound_arguments]") { + argparse::ArgumentParser program("test"); + + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-b") + .default_value(false) + .implicit_value(true); + + program.add_argument("-c") + .nargs(2) + .action([](const std::string& value) { return std::stof(value); }); + + program.parse_args({ "./main", "-cab", "3.14", "2.718" }); + + auto a = program.get("-a"); // true + auto b = program.get("-b"); // true + auto c = program.get>("-c"); // {3.14f, 2.718f} +} + +TEST_CASE("Parse out-of-order compound arguments. Second variation", "[compound_arguments]") { + argparse::ArgumentParser program("test"); + + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-b") + .default_value(false) + .implicit_value(true); + + program.add_argument("-c") + .nargs(2) + .default_value(std::vector{0.0f, 0.0f}) + .action([](const std::string& value) { return std::stof(value); }); + + program.parse_args({"./main", "-cb"}); + + auto a = program.get("-a"); + auto b = program.get("-b"); + auto c = program.get>("-c"); + + REQUIRE(a == false); + REQUIRE(b == true); + REQUIRE(program["-c"] == std::vector{0.0f, 0.0f}); +} \ No newline at end of file diff --git a/test/test_container_arguments.hpp b/test/test_container_arguments.hpp new file mode 100644 index 0000000..7b12e16 --- /dev/null +++ b/test/test_container_arguments.hpp @@ -0,0 +1,76 @@ +#pragma once +#include +#include + +TEST_CASE("Parse vector of arguments", "[vector]") { + argparse::ArgumentParser program("test"); + program.add_argument("input") + .nargs(2); + + program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv" }); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 2); + REQUIRE(inputs[0] == "rocket.mesh"); + REQUIRE(inputs[1] == "thrust_profile.csv"); +} + +TEST_CASE("Parse list of arguments", "[vector]") { + argparse::ArgumentParser program("test"); + program.add_argument("input") + .nargs(2); + + program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv" }); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 2); + REQUIRE(argparse::get_from_list(inputs, 0) == "rocket.mesh"); + REQUIRE(argparse::get_from_list(inputs, 1) == "thrust_profile.csv"); +} + +TEST_CASE("Parse list of arguments with default values", "[vector]") { + argparse::ArgumentParser program("test"); + program.add_argument("--input") + .default_value(std::list{1, 2, 3, 4, 5}) + .nargs(5); + + program.parse_args({ "test" }); + + auto inputs = program.get>("--input"); + REQUIRE(inputs.size() == 5); + REQUIRE(argparse::get_from_list(inputs, 0) == 1); + REQUIRE(argparse::get_from_list(inputs, 1) == 2); + REQUIRE(argparse::get_from_list(inputs, 2) == 3); + REQUIRE(argparse::get_from_list(inputs, 3) == 4); + REQUIRE(argparse::get_from_list(inputs, 4) == 5); + REQUIRE(program["--input"] == std::list{1, 2, 3, 4, 5}); +} + +TEST_CASE("Parse list of arguments and save in an object", "[vector]") { + + struct ConfigManager { + std::vector files; + void add_file(const std::string& file) { + files.push_back(file); + } + }; + + ConfigManager config_manager; + + argparse::ArgumentParser program("test"); + program.add_argument("--input_files") + .nargs(2) + .action([&](const std::string& value) { config_manager.add_file(value); return value; }); + + program.parse_args({ "test", "--input_files", "config.xml", "system.json" }); + + auto file_args = program.get>("--input_files"); + REQUIRE(file_args.size() == 2); + REQUIRE(file_args[0] == "config.xml"); + REQUIRE(file_args[1] == "system.json"); + + REQUIRE(config_manager.files.size() == 2); + REQUIRE(config_manager.files[0] == "config.xml"); + REQUIRE(config_manager.files[1] == "system.json"); + REQUIRE(program["--input_files"] == std::vector{"config.xml", "system.json"}); +} \ No newline at end of file diff --git a/test/test_optional_arguments.hpp b/test/test_optional_arguments.hpp new file mode 100644 index 0000000..151f6ad --- /dev/null +++ b/test/test_optional_arguments.hpp @@ -0,0 +1,46 @@ +#pragma once +#include +#include + +TEST_CASE("Parse toggle arguments with default value", "[optional_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("--verbose", "-v") + .default_value(false) + .implicit_value(true); + + program.parse_args({ "./test.exe" }); + REQUIRE(program.get("--verbose") == false); + REQUIRE(program["--verbose"] == false); +} + +TEST_CASE("Parse toggle arguments with implicit value", "[optional_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("--verbose") + .default_value(false) + .implicit_value(true); + + program.parse_args({ "./test.exe", "--verbose" }); + REQUIRE(program.get("--verbose") == true); + REQUIRE(program["--verbose"] == true); + REQUIRE(program["--verbose"] != false); +} + +TEST_CASE("Parse multiple toggle arguments with implicit values", "[optional_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-u") + .default_value(false) + .implicit_value(true); + + program.add_argument("-x") + .default_value(false) + .implicit_value(true); + + program.parse_args({ "./test.exe", "-a", "-x" }); + REQUIRE(program.get("-a") == true); + REQUIRE(program.get("-u") == false); + REQUIRE(program.get("-x") == true); +} \ No newline at end of file diff --git a/test/test_parent_parsers.hpp b/test/test_parent_parsers.hpp new file mode 100644 index 0000000..79a6d5c --- /dev/null +++ b/test/test_parent_parsers.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +TEST_CASE("Add parent parsers", "[parent_parsers]") { + argparse::ArgumentParser parent_parser("main"); + parent_parser.add_argument("--verbose") + .default_value(false) + .implicit_value(true); + + argparse::ArgumentParser child_parser("foo"); + child_parser.add_parents(parent_parser); + child_parser.parse_args({ "./main", "--verbose"}); + REQUIRE(child_parser["--verbose"] == true); +} + +TEST_CASE("Add parent to multiple parent parsers", "[parent_parsers]") { + argparse::ArgumentParser parent_parser("main"); + parent_parser.add_argument("--parent") + .default_value(0) + .action([](const std::string& value) { return std::stoi(value); }); + + argparse::ArgumentParser foo_parser("foo"); + foo_parser.add_argument("foo"); + foo_parser.add_parents(parent_parser); + foo_parser.parse_args({ "./main", "--parent", "2", "XXX" }); + REQUIRE(foo_parser["--parent"] == 2); + REQUIRE(foo_parser["foo"] == std::string("XXX")); + + argparse::ArgumentParser bar_parser("bar"); + bar_parser.add_argument("--bar"); + bar_parser.parse_args({ "./main", "--bar", "YYY" }); + REQUIRE(bar_parser["--bar"] == std::string("YYY")); +} \ No newline at end of file diff --git a/test/test_parse_args.hpp b/test/test_parse_args.hpp new file mode 100644 index 0000000..5fa500a --- /dev/null +++ b/test/test_parse_args.hpp @@ -0,0 +1,166 @@ +#pragma once +#include +#include + +TEST_CASE("Parse a string argument with value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--config"); + program.parse_args({ "test", "--config", "config.yml"}); + REQUIRE(program.get("--config") == "config.yml"); +} + +TEST_CASE("Parse a string argument with default value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--config") + .default_value(std::string("foo.yml")); + program.parse_args({ "test", "--config" }); + REQUIRE(program.get("--config") == "foo.yml"); +} + +TEST_CASE("Parse an int argument with value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--count") + .action([](const std::string& value) { return std::stoi(value); }); + program.parse_args({ "test", "--count", "5" }); + REQUIRE(program.get("--count") == 5); +} + +TEST_CASE("Parse an int argument with default value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--count") + .default_value(2) + .action([](const std::string& value) { return std::stoi(value); }); + program.parse_args({ "test", "--count" }); + REQUIRE(program.get("--count") == 2); +} + +TEST_CASE("Parse a float argument with value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--ratio") + .action([](const std::string& value) { return std::stof(value); }); + program.parse_args({ "test", "--ratio", "5.6645" }); + REQUIRE(program.get("--ratio") == 5.6645f); +} + +TEST_CASE("Parse a float argument with default value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--ratio") + .default_value(3.14f) + .action([](const std::string& value) { return std::stof(value); }); + program.parse_args({ "test", "--ratio" }); + REQUIRE(program.get("--ratio") == 3.14f); +} + +TEST_CASE("Parse a double argument with value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--ratio") + .action([](const std::string& value) { return std::stod(value); }); + program.parse_args({ "test", "--ratio", "5.6645" }); + REQUIRE(program.get("--ratio") == 5.6645); +} + +TEST_CASE("Parse a double argument with default value", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--ratio") + .default_value(3.14) + .action([](const std::string& value) { return std::stod(value); }); + program.parse_args({ "test", "--ratio" }); + REQUIRE(program.get("--ratio") == 3.14); +} + +TEST_CASE("Parse a vector of integer arguments", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--vector") + .nargs(5) + .action([](const std::string& value) { return std::stoi(value); }); + program.parse_args({ "test", "--vector", "1", "2", "3", "4", "5" }); + auto vector = program.get>("--vector"); + REQUIRE(vector.size() == 5); + REQUIRE(vector[0] == 1); + REQUIRE(vector[1] == 2); + REQUIRE(vector[2] == 3); + REQUIRE(vector[3] == 4); + REQUIRE(vector[4] == 5); +} + +TEST_CASE("Parse a vector of float arguments", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--vector") + .nargs(5) + .action([](const std::string& value) { return std::stof(value); }); + program.parse_args({ "test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5" }); + auto vector = program.get>("--vector"); + REQUIRE(vector.size() == 5); + REQUIRE(vector[0] == 1.1f); + REQUIRE(vector[1] == 2.2f); + REQUIRE(vector[2] == 3.3f); + REQUIRE(vector[3] == 4.4f); + REQUIRE(vector[4] == 5.5f); +} + +TEST_CASE("Parse a vector of double arguments", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--vector") + .nargs(5) + .action([](const std::string& value) { return std::stod(value); }); + program.parse_args({ "test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5" }); + auto vector = program.get>("--vector"); + REQUIRE(vector.size() == 5); + REQUIRE(vector[0] == 1.1); + REQUIRE(vector[1] == 2.2); + REQUIRE(vector[2] == 3.3); + REQUIRE(vector[3] == 4.4); + REQUIRE(vector[4] == 5.5); +} + +TEST_CASE("Parse a vector of string arguments", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--vector") + .nargs(5) + .action([](const std::string& value) { return value; }); + program.parse_args({ "test", "--vector", "abc", "def", "ghi", "jkl", "mno" }); + auto vector = program.get>("--vector"); + REQUIRE(vector.size() == 5); + REQUIRE(vector[0] == "abc"); + REQUIRE(vector[1] == "def"); + REQUIRE(vector[2] == "ghi"); + REQUIRE(vector[3] == "jkl"); + REQUIRE(vector[4] == "mno"); +} + +TEST_CASE("Parse a vector of character arguments", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("--vector") + .nargs(5) + .action([](const std::string& value) { return value[0]; }); + program.parse_args({ "test", "--vector", "a", "b", "c", "d", "e" }); + auto vector = program.get>("--vector"); + REQUIRE(vector.size() == 5); + REQUIRE(vector[0] == 'a'); + REQUIRE(vector[1] == 'b'); + REQUIRE(vector[2] == 'c'); + REQUIRE(vector[3] == 'd'); + REQUIRE(vector[4] == 'e'); +} + +TEST_CASE("Parse a vector of string arguments and construct objects", "[parse_args]") { + + class Foo { + public: + Foo(const std::string& value) : value(value) {} + std::string value; + }; + + argparse::ArgumentParser program("test"); + program.add_argument("--vector") + .nargs(5) + .action([](const std::string& value) { return Foo(value); }); + program.parse_args({ "test", "--vector", "abc", "def", "ghi", "jkl", "mno" }); + auto vector = program.get>("--vector"); + REQUIRE(vector.size() == 5); + REQUIRE(vector[0].value == Foo("abc").value); + REQUIRE(vector[1].value == Foo("def").value); + REQUIRE(vector[2].value == Foo("ghi").value); + REQUIRE(vector[3].value == Foo("jkl").value); + REQUIRE(vector[4].value == Foo("mno").value); +} \ No newline at end of file diff --git a/test/test_positional_arguments.hpp b/test/test_positional_arguments.hpp new file mode 100644 index 0000000..e69d55f --- /dev/null +++ b/test/test_positional_arguments.hpp @@ -0,0 +1,69 @@ +#pragma once +#include +#include + +TEST_CASE("Parse positional arguments", "[positional_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("input"); + program.add_argument("output"); + program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv" }); + REQUIRE(program.get("input") == "rocket.mesh"); + REQUIRE(program.get("output") == "thrust_profile.csv"); +} + +TEST_CASE("Parse positional arguments with fixed nargs", "[positional_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("input"); + program.add_argument("output").nargs(2); + program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "output.mesh" }); + REQUIRE(program.get("input") == "rocket.mesh"); + auto outputs = program.get>("output"); + REQUIRE(outputs.size() == 2); + REQUIRE(outputs[0] == "thrust_profile.csv"); + REQUIRE(outputs[1] == "output.mesh"); +} + +TEST_CASE("Parse positional arguments with optional arguments", "[positional_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("input"); + program.add_argument("output").nargs(2); + program.add_argument("--num_iterations") + .action([](const std::string& value) { return std::stoi(value); }); + program.parse_args({ "test", "rocket.mesh", "--num_iterations", "15", "thrust_profile.csv", "output.mesh" }); + REQUIRE(program.get("--num_iterations") == 15); + REQUIRE(program.get("input") == "rocket.mesh"); + auto outputs = program.get>("output"); + REQUIRE(outputs.size() == 2); + REQUIRE(outputs[0] == "thrust_profile.csv"); + REQUIRE(outputs[1] == "output.mesh"); +} + +TEST_CASE("Parse positional arguments with optional arguments in the middle", "[positional_arguments]") { + argparse::ArgumentParser program("test"); + program.add_argument("input"); + program.add_argument("output").nargs(2); + program.add_argument("--num_iterations") + .action([](const std::string& value) { return std::stoi(value); }); + program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "--num_iterations", "15", "output.mesh" }); + REQUIRE(program.get("--num_iterations") == 15); + REQUIRE(program.get("input") == "rocket.mesh"); + auto outputs = program.get>("output"); + REQUIRE(outputs.size() == 2); + REQUIRE(outputs[0] == "thrust_profile.csv"); + REQUIRE(outputs[1] == "output.mesh"); +} + +TEST_CASE("Square a number", "[positional_arguments]") { + argparse::ArgumentParser program; + program.add_argument("--verbose", "-v") + .help("enable verbose logging") + .default_value(false) + .implicit_value(true); + + program.add_argument("square") + .help("display a square of a given number") + .action([](const std::string& value) { return pow(std::stoi(value), 2); }); + + program.parse_args({"./main", "15"}); + REQUIRE(program.get("square") == 225); +} \ No newline at end of file