diff --git a/BUILD b/BUILD index 099900c..74ac416 100644 --- a/BUILD +++ b/BUILD @@ -4,7 +4,7 @@ cc_library( name = "tinytest", srcs = ["tinytest.cpp"], hdrs = ["tinytest.h"], - includes = ["."], + includes = ["*.h"], visibility = ["//visibility:public"], ) diff --git a/tinytest.cpp b/tinytest.cpp index 2e9e1c1..daad35d 100644 --- a/tinytest.cpp +++ b/tinytest.cpp @@ -64,78 +64,78 @@ TestResults::TestResults(uint32_t errors, skipped_(skipped), total_(total) {} -TestResults& TestResults::error() { +TestResults& TestResults::Error() { errors_++; return *this; } -TestResults& TestResults::error(string message) { +TestResults& TestResults::Error(string message) { errors_++; error_messages_.push_back(message); return *this; } -TestResults& TestResults::fail() { +TestResults& TestResults::Fail() { total_++; failed_++; return *this; } -TestResults& TestResults::fail(const string& message) { +TestResults& TestResults::Fail(const string& message) { total_++; failed_++; failure_messages_.push_back(message); return *this; } -vector TestResults::failure_messages() { +vector TestResults::FailureMessages() const { return failure_messages_; } -TestResults& TestResults::pass() { +TestResults& TestResults::Pass() { total_++; passed_++; return *this; } -TestResults& TestResults::skip() { +TestResults& TestResults::Skip() { total_++; skipped_++; return *this; } -TestResults& TestResults::skip(const string& message) { +TestResults& TestResults::Skip(const string& message) { total_++; skipped_++; skip_messages_.push_back(message); return *this; } -vector TestResults::skip_messages() { +vector TestResults::SkipMessages() const { return skip_messages_; } -vector TestResults::error_messages() { +vector TestResults::ErrorMessages() const { return error_messages_; } -uint32_t TestResults::errors() { +uint32_t TestResults::Errors() const { return errors_; } -uint32_t TestResults::failed() { +uint32_t TestResults::Failed() const { return failed_; } -uint32_t TestResults::passed() { +uint32_t TestResults::Passed() const { return passed_; } -uint32_t TestResults::skipped() { +uint32_t TestResults::Skipped() const { return skipped_; } -uint32_t TestResults::total() { +uint32_t TestResults::Total() const { return total_; } @@ -173,32 +173,32 @@ TestResults& TestResults::operator+=(const TestResults& other) { } void PrintResults(std::ostream& os, TestResults results) { - auto skip_messages = results.skip_messages(); + auto skip_messages = results.SkipMessages(); if (skip_messages.size() > 0) { os << "Skipped:" << endl; for_each(skip_messages.begin(), skip_messages.end(), [&os](const string& message) { os << "🚧Skipped: " << message << endl; }); } - auto failure_messages = results.failure_messages(); + auto failure_messages = results.FailureMessages(); if (failure_messages.size() > 0) { os << "Failures:" << endl; for_each(failure_messages.begin(), failure_messages.end(), [&os](const string& message) { os << "❌FAILED: " << message << endl; }); } - auto error_messages = results.error_messages(); + auto error_messages = results.ErrorMessages(); if (error_messages.size() > 0) { os << "Errors:" << endl; for_each(error_messages.begin(), error_messages.end(), [&os](const string& message) { os << "🔥ERROR: " << message << endl; }); } - os << "Total tests: " << results.total() << endl; - os << "Passed: " << results.passed() << " ✅" << endl; - os << "Failed: " << results.failed() << " ❌" << endl; - os << "Skipped: " << results.skipped() << " 🚧" << endl; - os << "Errors: " << results.errors() << " 🔥" << endl; + os << "Total tests: " << results.Total() << endl; + os << "Passed: " << results.Passed() << " ✅" << endl; + os << "Failed: " << results.Failed() << " ❌" << endl; + os << "Skipped: " << results.Skipped() << " 🚧" << endl; + os << "Errors: " << results.Errors() << " 🔥" << endl; } // End TestResults methods. @@ -206,4 +206,41 @@ void PrintResults(std::ostream& os, TestResults results) { MaybeTestConfigureFunction DefaultTestConfigureFunction() { return std::nullopt; } + +MaybeTestConfigureFunction Coalesce(MaybeTestConfigureFunction first, MaybeTestConfigureFunction second) { + if (first.has_value()) { + if (second.has_value()) { + // This is the only place we actually need to combine them. + return [&first, &second]() { + first.value()(); + second.value()(); + }; + } else { + return first; + } + } else { + return second; + } +} + +// Utility functions. +TestResults& SkipTest(TestResults& results, + const std::string& suite_label, + const std::string& test_label, + std::optional reason) { + std::string qualified_test_label = suite_label + "::" + test_label; + std::cout << " 🚧Skipping Test: " << test_label; + if (reason.has_value()) { + std::cout << " because " << reason.value(); + } + std::cout << std::endl; + results.Skip(qualified_test_label + (reason.has_value() ? " because " + reason.value() : "")); + return results; +} + +// TODO: Factor out the pretty printing into a separate module so it can be tested separately. +// TODO: Consider making separate files for test suite, tests, test cases, and test results. +// TODO: Come up with a way to autogenerat a main function that runs all tests in a *_test.cpp file. +// TODO: Come up with a way to aggregate TestResults over multiple c++ files when running under bazel. +// TODO: Create a Makefile to build as a library. } // namespace TinyTest diff --git a/tinytest.h b/tinytest.h index 2d87446..a1b5ae3 100644 --- a/tinytest.h +++ b/tinytest.h @@ -10,58 +10,154 @@ #include #include +#include #include #include +#include #include #include +#include #include #include #include -// TODO: Document this. -// Tuple printer from: -// https://stackoverflow.com/questions/6245735/pretty-print-stdtuple/31116392#58417285 -template -auto& operator<<(std::basic_ostream& os, std::tuple const& t) { - std::apply([&os](auto&&... args) { ((os << args << " "), ...); }, t); +namespace TinyTest { +// Begin EscapeForPrinting +template +std::basic_string_view EscapeForPrinting(const std::basic_string_view& text) { + return std::regex_replace(text, std::regex("\033"), "\\033"); +} + +template +std::basic_string EscapeForPrinting(const std::basic_string& text) { + return std::regex_replace(text, std::regex("\033"), "\\033"); +} + +template +std::basic_string EscapeForPrinting(const TChar* text) { + return std::regex_replace(text, std::regex("\033"), "\\033"); +} + +// End EscapeForPrinting + +// Begin PrettyPrint +// std::string_view. +template +auto& PrettyPrint(std::basic_ostream& os, const std::basic_string_view& item) { + os << "\"" << EscapeForPrinting(item) << "\""; return os; } -// TODO: Document this. -template -auto& operator<<(std::basic_ostream& os, std::vector v) { +// std::string. +template +auto& PrettyPrint(std::basic_ostream& os, const std::basic_string& item) { + os << "\"" << EscapeForPrinting(item) << "\""; + return os; +} + +// const char*. +template +auto& PrettyPrint(std::basic_ostream& os, const TChar* item) { + os << "\"" << EscapeForPrinting(item) << "\""; + return os; +} + +// tuple<...> +template +auto& PrettyPrint(std::basic_ostream os, const std::tuple& tuple) { + std::apply( + [&os](auto&&... args) { + if (sizeof...(TArgs) == 0) { + os << "[]"; + return; + } + size_t n = 0; + os << "[ "; + ((PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? ", " : "")), ...); + os << " ]"; + }, + tuple); + return os; +} + +// containers +template ().begin()), decltype(std::declval().end())>>, + typename = std::enable_if_t().begin())>::iterator_category>>> +auto& PrettyPrint(std::basic_ostream& os, TContainer container) { os << "[ "; - for (auto it = v.begin(); it != v.end(); it++) { - if (it != v.begin()) { + for (auto it = container.begin(); it != container.end(); it++) { + if (it != container.begin()) { os << ", "; } - os << *it; + PrettyPrint(os, *it); } os << " ]"; return os; } -// TODO: Document this. +// Catch-all for everything else. template -auto& compare(std::basic_ostream& error_message, - std::vector expected, - std::vector actual) { - if (expected.size() != actual.size()) { - error_message << "size mismatch expected: " << expected.size() << ", actual: " << actual.size(); - return error_message; - } - - for (size_t index = 0; index < expected.size(); index++) { - if (expected[index] != actual[index]) { - error_message << "vectors differ at index " << index << ", \"" << expected[index] << "\" != \"" << actual[index] - << "\", expected: \"" << expected << "\", actual: \"" << actual << "\""; - return error_message; - } - } - return error_message; +auto& PrettyPrint(std::basic_ostream& os, TItem item) { + os << item; + return os; +} + +// End PrettyPrint + +// Begin PrettyPrintWithSeparator +// Prints args with separator between them. std::string_view separator. +template +auto& PrettyPrintWithSeparator(std::basic_ostream os, + std::basic_string_view separator, + TArgs&&... args) { + (((PrettyPrint(os, args), os), EscapeForPrinting(separator)), ...); + return os; +} + +// Prints args with separator between them. std::string separator. +template +auto& PrettyPrintWithSeparator(std::basic_ostream os, + std::basic_string separator, + TArgs&&... args) { + (((PrettyPrint_item(os, args), os), EscapeForPrinting(separator)), ...); + return os; +} + +// Prints args with separator between them. const char* separator. +template +auto& PrettyPrintWithSeparator(std::basic_ostream os, const TChar* separator, Args&&... args) { + ((os << args << EscapeForPrinting(separator)), ...); + return os; +} + +// End PrettyPrintWithSeparator + +//////////////////// + +// TODO: Document this. +template +std::string InterceptCout(std::function fnToExecute, + std::optional> maybe_args = std::nullopt) { + std::ostringstream os; + auto saved_buffer = std::cout.rdbuf(); + std::cout.rdbuf(os.rdbuf()); + // TODO: run the function + + if (maybe_args.has_value()) { + std::apply(fnToExecute, maybe_args.value()); + } else { + std::invoke(fnToExecute); + } + std::cout.rdbuf(saved_buffer); + return os.str(); } -namespace TinyTest { /// @brief class TestResults { public: @@ -93,69 +189,69 @@ class TestResults { /// @brief Adds an error. This increments errors. /// @return A reference to this instance. Used for chaining. - TestResults& error(); + TestResults& Error(); /// @brief Adds an error with a message. This increments errors as well as /// saving the error message. /// @param message The error message. /// @return A reference to this instance. Used for chaining. - TestResults& error(std::string message); + TestResults& Error(std::string message); /// @brief Adds a failed test. This increments total and failed. /// @return A reference to this instance. Used for chaining. - TestResults& fail(); + TestResults& Fail(); /// @brief Adds a failed test with a message. This increments total and failed /// as well as saving the failure message. /// @param message The reason the test failed. /// @return A reference to this instance. Used for chaining. - TestResults& fail(const std::string& message); + TestResults& Fail(const std::string& message); /// @brief Adds a passed test. This increments total and passed. /// @return A reference to this instance. Used for chaining. - TestResults& pass(); + TestResults& Pass(); /// @brief Adds a skipped test. This increments total and skipped. /// @return A reference to this instance. Used for chaining. - TestResults& skip(); + TestResults& Skip(); /// @brief Adds a skipped test with a message. This increments total and /// skipped as well as saving the skip message. /// @param message The reason the test was skipped. /// @return A reference to this instance. Used for chaining. - TestResults& skip(const std::string& message); + TestResults& Skip(const std::string& message); /// @brief Getter for the list of error messages. /// @return - std::vector error_messages(); + std::vector ErrorMessages() const; /// @brief Getter for the count of errors. /// @return - uint32_t errors(); + uint32_t Errors() const; /// @brief Getter for the count of failed tests. /// @return The count of failed tests. - uint32_t failed(); + uint32_t Failed() const; /// @brief Getter for the list of failure messages. /// @return The list of failure messages. - std::vector failure_messages(); + std::vector FailureMessages() const; /// @brief Getter for the count of passed tests. /// @return The count of passed tests. - uint32_t passed(); + uint32_t Passed() const; /// @brief Getter for the count of skipped tests. /// @return The count of skipped tests. - uint32_t skipped(); + uint32_t Skipped() const; /// @brief Getter for the list of skip messages. /// @return The list of skip messages. - std::vector skip_messages(); + std::vector SkipMessages() const; /// @brief Getter for the count of total tests. /// @return The count of total tests run. - uint32_t total(); + uint32_t Total() const; /// @brief Returns the combination of this and another TestResults instance. /// @param other The other TestResults instance to add to this one. @@ -214,8 +310,8 @@ using TestTuple = std::tuple /* input_params - The input parameters for this test. These will be used when calling std::apply with function_to_test to execute the test. */ , - MaybeTestCompareFunction /* test_compare_function - If this is not nullprt then this function - will be called instead of suite_compare_function to determine if the + MaybeTestCompareFunction /* test_Compare_function - If this is not nullprt then this function + will be called instead of suite_Compare_function to determine if the test passes. Use this to check for side effects of the test. Return true if the test passes and false otherwise. */ , @@ -236,89 +332,17 @@ using TestTuple = template using TestSuite = std::tuple, - std::vector>, + std::initializer_list>, MaybeTestCompareFunction, MaybeTestConfigureFunction, MaybeTestConfigureFunction, bool>; -// This function is called to execute a test suite. You provide it with some -// configuration info, optional utility callback functions, and test data (input -// parameters for each call to function_to_test and the expected result). It -// returns a TestResults that should be treated as an opaque data type. Not all -// parameters are named in code, but they are named and explained in the -// comments and will be described by those names below. -// string suite_name - This is the name of this test suite. It is used for -// reporting messages. TFunctionToTest function_to_test - This is the function -// to test. This may be replaced if necessary by std::function. It may not -// currently support class methods, but that is planned. vector> -// tests - This is the test run data. Each tuple in the vector is a single -// test run. It's members are explained below. -// string test_name - This is the name of this test. It is used for -// reporting messages. TResult expected_output - This is the expected result -// of executing this test. bool(*)(const TResult expected, const TResult -// actual) test_compare_function - This is optional. If unset or set to -// nullptr it is skipped. If set to a function it is called to evaluate the -// test results. It takes the expected and actual results as parameters and -// should return true if the test passed and false otherwise. This may be -// changed to return a TestResults at some point. void(*)(TInputParams...) -// test_setup_function - This is optional. If unset or set to nullptr it is -// skipped. If set to a function it is called before each test to setup the -// environment for the test. You may use it to allocate resources and setup -// mocks, stubs, and spies. void(*)(TInputParams...) test_teardown_function -// - This is optiona. If unset or set to nullptr it is skipped. If set to a -// function it is called after each test to cleanup the environment after -// the test. You should free resources allocated by test_setup_function. -// bool is_enabled - This is optional. If unset or set to true the test is -// run. If set to false this test is skipped. If skipped it will be reported -// as a skipped/disabled test. -// bool(*)(const TResult expected, const TResult actual) -// suite_compare_function - This is optional. If unset or set to nullptr it is -// skipped. If set to a function and test_compare_function is not called for a -// test run then this function is called to evaluate the test results. It -// takes the expected and actual results as parameters and should return true -// if the test passed and false otherwise. This may be changed to return a -// TestResults at some point. void(*)() suite_setup_function - This is -// optional. If unset or set to nullptr it is skipped. If set to a function it -// is called before starting this test suite to setup the environment. You may -// use it to allocate resources and setup mocks, stubs, and spies. void(*)() -// suite_teardown_function - This is optional. If unset or set to nullptr it -// is skipped. If set to a function it is called after all tests in this suite -// have finished and all reporting has finished. You should free resources -// allocated by suite_setup_function. -// This method should be called like so. This is the minimal call and omits all -// of the optional params. This is the most common usage. You should put one -// tuple of inputs and expected output for each test case. -// results = collect_and_report_test_resultstest_fn( -// "Test: function_under_test", -// function_under_test, -// vector({ -// make_tuple( -// "ShouldReturnAppleForGroupId_1_and_ItemId_2", -// string("Apple"), -// make_tuple(1,2), -// ), -// }), -// ); -// The suites can be run from one file as such. From a file called -// ThingDoer_test.cpp to test the class/methods ThingDoer declared in -// ThingDoer.cpp. This isn't mandatory but is a best practice. You can use -// function_to_test without calling collect_and_report_test_results() and also -// could call it from a normal int main(int argc, char** argv) or other -// function. -// TestResults test_main_ThingDoer(int argc, char* argv[]) { -// TestResults results; -// results = collect_and_report_test_results(results, -// function_to_test("do_thing1", ...), argc, argv); results = -// collect_and_report_test_results(results, function_to_test("do_thing2", -// ...), argc, argv); return results; -// } -// Then some test harness either generated or explicit can call -// test_main_ThingDoer(...) and optionally reported there. Reporting granularity -// is controlled by how frequently you call -// collect_and_report_test_results(...). You can combine test results with -// results = results + function_to_test(..); and then -// collect_and_report_test_results on the aggregate TestResults value. +TestResults& SkipTest(TestResults& results, + const std::string& suite_label, + const std::string& test_label, + std::optional reason = std::nullopt); + /// @brief /// @tparam TResult The result type of the test. /// @tparam TInputParams... The types of parameters sent to the test function. @@ -327,25 +351,36 @@ using TestSuite = std::tuple made from each item in tests. /// @param tests A std::vector of test runs. -/// @param suite_compare_function A function used to compare the expected and -/// actual test results. This can be overridden per test by setting -/// test_compare_function. +/// @param suite_Compare A function used to Compare the expected and actual test +/// results. This can be overridden per test by setting test_Compare. /// @param after_all This is called before each suite is started to setup the /// environment. This is where you should build mocks, setup spies, and test /// fixtures. /// @param before_all This is called after each suite has completed to cleanup /// anything allocated in suite_before_each. -/// @param is_enabled If false none of these tests are run and they are all -/// reported as skipped. +/// @param is_enabled If false the test is reported as skipped. If true the test +/// is run as normal. template -TestResults execute_suite(std::string suite_label, - std::function function_to_test, - std::vector> tests, - MaybeTestCompareFunction suite_compare = std::nullopt, - MaybeTestConfigureFunction before_all = std::nullopt, - MaybeTestConfigureFunction after_all = std::nullopt, - bool is_enabled = true) { +TestResults ExecuteSuite(std::string suite_label, + std::function function_to_test, + std::initializer_list> tests, + MaybeTestCompareFunction suite_Compare = std::nullopt, + MaybeTestConfigureFunction before_all = std::nullopt, + MaybeTestConfigureFunction after_all = std::nullopt, + bool is_enabled = true) { TestResults results; + if (!is_enabled) { + std::cout << "🚧Skipping suite: " << suite_label << " because it is disabled." << std::endl; + for (auto test : tests) { + std::string test_label = std::get<0>(test); + SkipTest(results, suite_label, test_label, "the suite is disabled."); + } + return results; + } + if (tests.size() == 0) { + std::cout << "🚧Skipping suite: " << suite_label << " because it is empty." << std::endl; + return results; + } std::cout << "🚀Beginning Suite: " << suite_label << std::endl; // Step 1: Suite Setup @@ -357,29 +392,28 @@ TestResults execute_suite(std::string suite_label, // Step 2: Execute Tests for_each(tests.begin(), tests.end(), - [&suite_label, &function_to_test, &results, &suite_compare](TestTuple test_data) { + [&suite_label, &function_to_test, &results, &suite_Compare](TestTuple test_data) { // Step 2a: Extract our variables from the TestTuple. - const std::string& test_name = std::get<0>(test_data); - const std::string qualified_test_name = suite_label + "::" + test_name; + const std::string& test_label = std::get<0>(test_data); + const std::string qualified_test_label = suite_label + "::" + test_label; const TResult& expected_output = std::get<1>(test_data); std::tuple input_params = std::get<2>(test_data); - MaybeTestCompareFunction maybe_compare_function = std::get<3>(test_data); - TestCompareFunction compare_function = - maybe_compare_function.has_value() ? *maybe_compare_function - : suite_compare.has_value() ? *suite_compare + MaybeTestCompareFunction maybe_Compare_function = std::get<3>(test_data); + TestCompareFunction Compare_function = + maybe_Compare_function.has_value() ? *maybe_Compare_function + : suite_Compare.has_value() ? *suite_Compare : [](const TResult& l, const TResult& r) { return l == r; }; MaybeTestConfigureFunction before_each = std::get<4>(test_data); MaybeTestConfigureFunction after_each = std::get<5>(test_data); bool is_enabled = std::get<6>(test_data); if (!is_enabled) { - std::cout << " 🚧Skipping Test: " << test_name << std::endl; - results.skip(qualified_test_name); + SkipTest(results, suite_label, test_label); return; } // Step 2b: Test Setup - std::cout << " Beginning Test: " << test_name << std::endl; + std::cout << " Beginning Test: " << test_label << std::endl; if (before_each.has_value()) { (*before_each)(); } @@ -390,30 +424,35 @@ TestResults execute_suite(std::string suite_label, actual = std::apply(function_to_test, input_params); } catch (const std::exception& ex) { std::ostringstream os; - os << "Caught exception \"" << ex.what() << "\""; - results.error(qualified_test_name + " " + os.str()); + os << "Caught exception \"" << ex.what() << "\"."; + results.Error(qualified_test_label + " " + os.str()); std::cout << " 🔥ERROR: " << os.str() << std::endl; } catch (const std::string& message) { std::ostringstream os; - os << "Caught string \"" << message << "\""; - results.error(qualified_test_name + " " + os.str()); + os << "Caught string \"" << message << "\"."; + results.Error(qualified_test_label + " " + os.str()); + std::cout << " 🔥ERROR: " << os.str() << std::endl; + } catch (const char* message) { + std::ostringstream os; + os << "Caught c-string \"" << message << "\"."; + results.Error(qualified_test_label + " " + os.str()); std::cout << " 🔥ERROR: " << os.str() << std::endl; } catch (...) { std::string message = "Caught something that is neither an std::exception " "nor an std::string."; - results.error(qualified_test_name + " " + message); + results.Error(qualified_test_label + " " + message); std::cout << " 🔥ERROR: " << message << std::endl; } // Step 2d: Pass or fail. - if (compare_function(expected_output, actual)) { - results.pass(); + if (Compare_function(expected_output, actual)) { + results.Pass(); std::cout << " ✅PASSED" << std::endl; } else { std::ostringstream os; os << "expected: \"" << expected_output << "\", actual: \"" << actual << "\""; - results.fail(qualified_test_name + " " + os.str()); + results.Fail(qualified_test_label + " " + os.str()); std::cout << " ❌FAILED: " << os.str() << std::endl; } @@ -421,7 +460,7 @@ TestResults execute_suite(std::string suite_label, if (after_each.has_value()) { (*after_each)(); } - std::cout << " Ending Test: " << test_name << std::endl; + std::cout << " Ending Test: " << test_label << std::endl; }); // Step 3: Suite Teardown @@ -435,30 +474,17 @@ TestResults execute_suite(std::string suite_label, /// @brief /// @tparam TResult The result type of the test. /// @tparam TInputParams... The types of parameters sent to the test function. -/// @param suite_label The label for this test suite. For example a class name -/// such as "MortgageCalculator". -/// @param function_to_test The function to be tested. It will be called with -/// std::apply and a std::tuple made from each item in tests. -/// @param tests A std::vector of test runs. -/// @param suite_compare A function used to compare the expected and actual test -/// results. This can be overridden per test by setting test_compare. -/// @param after_all This is called before each suite is started to setup the -/// environment. This is where you should build mocks, setup spies, and test -/// fixtures. -/// @param before_all This is called after each suite has completed to cleanup -/// anything allocated in suite_before_each. -/// @param is_enabled If false the test is reported as skipped. If true the test -/// is run as normal. +/// @param test_suite A tuple representing the test suite configuration. template -TestResults execute_suite(std::string suite_label, - std::function function_to_test, - std::initializer_list> tests, - MaybeTestCompareFunction suite_compare = std::nullopt, - MaybeTestConfigureFunction before_all = std::nullopt, - MaybeTestConfigureFunction after_all = std::nullopt, - bool is_enabled = true) { - std::vector test_data = std::vector(tests); - return execute_suite(suite_label, function_to_test, tests, suite_compare, before_all, after_all, is_enabled); +TestResults ExecuteSuite(const TestSuite& test_suite) { + std::string suite_label = std::get<0>(test_suite); + std::function function_to_test = std::get<1>(test_suite); + std::initializer_list> tests = std::get<2>(test_suite); + MaybeTestCompareFunction suite_Compare = sizeof(test_suite) > 3 ? std::get<3>(test_suite) : std::nullopt; + MaybeTestConfigureFunction before_all = sizeof(test_suite) > 4 ? std::get<4>(test_suite) : std::nullopt; + MaybeTestConfigureFunction after_all = sizeof(test_suite) > 5 ? std::get<5>(test_suite) : std::nullopt; + bool is_enabled = sizeof(test_suite) > 6 ? std::get<6>(test_suite) : true; + return ExecuteSuite(suite_label, function_to_test, tests, suite_Compare, before_all, after_all, is_enabled); } /// @brief @@ -470,7 +496,7 @@ TestResults execute_suite(std::string suite_label, /// input parameters. /// @param input_params The input parameters to use when calling the test /// function. -/// @param test_compare_fn An optional function that can be used to compare the +/// @param test_Compare_fn An optional function that can be used to Compare the /// expected and actual return values. This is good for when you only care about /// certain fields being equal. /// @param before_each This is called to setup the environment before running @@ -482,14 +508,14 @@ TestResults execute_suite(std::string suite_label, /// skipped for reporting purposes. /// @return A TestTuple suitable for use as a test run when calling test_fn. template -TestTuple make_test(const std::string& test_name, - const TResult& expected, - std::tuple input_params, - MaybeTestCompareFunction test_compare_fn = std::nullopt, - MaybeTestConfigureFunction before_each = std::nullopt, - MaybeTestConfigureFunction after_each = std::nullopt, - bool is_enabled = true) { - return make_tuple(test_name, expected, input_params, test_compare_fn, before_each, after_each, is_enabled); +TestTuple MakeTest(const std::string& test_name, + const TResult& expected, + std::tuple input_params, + MaybeTestCompareFunction test_Compare_fn = std::nullopt, + MaybeTestConfigureFunction before_each = std::nullopt, + MaybeTestConfigureFunction after_each = std::nullopt, + bool is_enabled = true) { + return make_tuple(test_name, expected, input_params, test_Compare_fn, before_each, after_each, is_enabled); } /// @brief @@ -499,71 +525,114 @@ TestTuple make_test(const std::string& test_name, /// @param suite_name /// @param function_to_test /// @param test_data -/// @param compare +/// @param Compare /// @param before_each /// @param after_each /// @param is_enabled /// @return template -TestSuite make_test_suite(const std::string& suite_name, - TFunctionToTest function_to_test, - std::vector> test_data, - MaybeTestCompareFunction compare = std::nullopt, - MaybeTestConfigureFunction before_each = std::nullopt, - MaybeTestConfigureFunction after_each = std::nullopt, - bool is_enabled = true) { - return make_tuple(suite_name, function_to_test, test_data, compare, before_each, after_each, is_enabled); -} - -template -TestSuite make_test_suite( - const std::string& suite_name, - TFunctionToTest function_to_test, - std::initializer_list> test_data, - MaybeTestCompareFunction compare = std::nullopt, - MaybeTestConfigureFunction before_each = std::nullopt, - MaybeTestConfigureFunction after_each = std::nullopt, - bool is_enabled = true) { - return make_tuple(suite_name, function_to_test, test_data, compare, before_each, after_each, is_enabled); -} - -/// @brief -/// @tparam TResult The result type of the test. -/// @tparam TInputParams... The types of parameters sent to the test function. -/// @param test_suite A tuple representing the test suite configuration. -template -TestResults execute_suite(const TestSuite& test_suite) { - return execute_suite( - std::get<0>(test_suite), std::get<1>(test_suite), std::get<2>(test_suite) - // TODO: make this work for the optional parts of the tuple too. - ); -} - -/// @brief -/// @tparam ...TInputParams -/// @param first -/// @param second -/// @return -template -MaybeTestConfigureFunction coalesce(MaybeTestConfigureFunction first, MaybeTestConfigureFunction second) { - if (first.has_value()) { - if (second.has_value()) { - // This is the only place we actually need to combine them. - return [&first, &second](TInputParams... input_params) { - *first(input_params...); - *second(input_params...); - }; - } else { - return first; - } - } else { - return second; - } +TestSuite MakeTestSuite(const std::string& suite_name, + TFunctionToTest function_to_test, + std::initializer_list> test_data, + MaybeTestCompareFunction Compare = std::nullopt, + MaybeTestConfigureFunction before_each = std::nullopt, + MaybeTestConfigureFunction after_each = std::nullopt, + bool is_enabled = true) { + return make_tuple(suite_name, function_to_test, test_data, Compare, before_each, after_each, is_enabled); } /// @brief Writes a friendly version of results to the provided stream. /// @param os The stream to write to. /// @param results The TestResults to write. void PrintResults(std::ostream& os, TestResults results); + +/// @brief +/// @param first +/// @param second +/// @return +MaybeTestConfigureFunction Coalesce(MaybeTestConfigureFunction first, MaybeTestConfigureFunction second); + +// TODO: Document this. +// Tuple printer based on code from: +// https://stackoverflow.com/questions/6245735/pretty-print-stdtuple/31116392#58417285 +template +auto& operator<<(std::basic_ostream& os, std::tuple const& t) { + std::apply( + [&os](auto&&... args) { + if (sizeof...(TArgs) == 0) { + os << "[]"; + return; + } + size_t n = 0; + os << "[ "; + ((TinyTest::PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? ", " : "")), ...); + os << " ]"; + }, + t); + return os; +} + +// TODO: Document this. +template +auto& operator<<(std::basic_ostream& os, const void* pointer) { + os << pointer; +} + +// TODO: Simplify this. +template ().begin()), decltype(std::declval().end())>>, + typename = std::enable_if_t().begin())>::iterator_category>>> +auto& operator<<(std::basic_ostream& os, TContainer container) { + os << "[ "; + for (auto it = container.begin(); it != container.end(); it++) { + if (it != container.begin()) { + os << ", "; + } + TinyTest::PrettyPrint(os, *it); + } + os << " ]"; + return os; +} + +// TODO: Document this. +template +auto& operator<<(std::basic_ostream& os, std::initializer_list container) { + os << "[ "; + for (auto it = container.begin(); it != container.end(); it++) { + if (it != container.begin()) { + os << ", "; + } + TinyTest::PrettyPrint(os, *it); + } + os << " ]"; + return os; +} + +// TODO: Document this. +template +auto& Compare(std::basic_ostream& error_message, + std::vector expected, + std::vector actual) { + if (expected.size() != actual.size()) { + error_message << "size mismatch expected: " << expected.size() << ", actual: " << actual.size(); + return error_message; + } + + for (size_t index = 0; index < expected.size(); index++) { + if (expected[index] != actual[index]) { + error_message << "vectors differ at index " << index << ", \"" << expected[index] << "\" != \"" << actual[index] + << "\", expected: \"" << expected << "\", actual: \"" << actual << "\""; + return error_message; + } + } + return error_message; +} + } // End namespace TinyTest + #endif // End !defined TEST_H__ diff --git a/tinytest_test.cpp b/tinytest_test.cpp index e69de29..5395dd5 100644 --- a/tinytest_test.cpp +++ b/tinytest_test.cpp @@ -0,0 +1,1322 @@ +/*************************************************************************************** + * @file tinytest.h * + * * + * @brief Defines structs and functions for implementing TinyTest. * + * @copyright Copyright 2023 Tom Hicks * + * Licensed under the MIT license see the LICENSE file for details. * + ***************************************************************************************/ + +#include "tinytest.h" + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { +using std::function; +using std::get; +using std::make_tuple; +using std::nullopt; +using std::ostringstream; +using std::string; +using std::tuple; +using std::vector; +using testing::Eq; +using testing::Ne; +using TinyTest::Coalesce; +using TinyTest::Compare; +using TinyTest::DefaultTestCompareFunction; +using TinyTest::DefaultTestConfigureFunction; +using TinyTest::ExecuteSuite; +using TinyTest::InterceptCout; +using TinyTest::MakeTest; +using TinyTest::MakeTestSuite; +using TinyTest::MaybeTestCompareFunction; +using TinyTest::MaybeTestConfigureFunction; +using TinyTest::PrintResults; +using TinyTest::TestResults; +using TinyTest::TestSuite; +using TinyTest::TestTuple; +using TinyTest::operator<<; + +TEST(TuplePrinter, ShouldPrintAnEmptyTuple) { + ostringstream os; + auto tuple = make_tuple(); + os << tuple; + // Ideally something like "[]" or "Tuple: []" + EXPECT_THAT(os.str(), Eq("[]")); +} + +TEST(TuplePrinter, ShouldPrintATupleOfOneString) { + ostringstream os; + auto tuple = make_tuple("asdf"); + os << tuple; + // Ideally this wouldn't have a space at the end. + EXPECT_THAT(os.str(), Eq("[ \"asdf\" ]")); +} + +TEST(TuplePrinter, ShouldPrintATupleOfTwoIntegers) { + ostringstream os; + auto tuple = make_tuple(69, 420); + os << tuple; + EXPECT_THAT(os.str(), Eq("[ 69, 420 ]")); +} + +TEST(TuplePrinter, ShouldPrintATupleOfTwoStrings) { + ostringstream os; + auto tuple = make_tuple("first", "second"); + os << tuple; + EXPECT_THAT(os.str(), Eq("[ \"first\", \"second\" ]")); +} + +TEST(TuplePrinter, ShouldPrintATupleOfOneStringAndOneInteger) { + ostringstream os; + auto tuple = make_tuple("this is a string that ends with 69", 420); + os << tuple; + EXPECT_THAT(os.str(), Eq("[ \"this is a string that ends with 69\", 420 ]")); +} + +TEST(TuplePrinter, ShouldPringATupleOfStringsContainingSpaces) { + ostringstream os; + auto tuple = make_tuple(" ", " ", " ", " "); + os << tuple; + EXPECT_THAT(os.str(), Eq("[ \" \", \" \", \" \", \" \" ]")); +} + +TEST(VectorPrinter, ShouldPrintAnEmptyVector) { + ostringstream os; + vector value = vector({1, 2, 3, 4}); + os << value; + EXPECT_THAT(os.str(), Eq("[ 1, 2, 3, 4 ]")); +} + +TEST(VectorPrinter, ShouldPrintAVectorOfCStrings) { + ostringstream os; + vector value = vector({"asdf", "fdsa", "lemon", "cherry"}); + os << value; + // Ideally "[ \"asdf\", \"fdsa\", \"lemon\", \"cherry\" ]" + EXPECT_THAT(os.str(), Eq("[ \"asdf\", \"fdsa\", \"lemon\", \"cherry\" ]")); +} + +TEST(VectorPrinter, ShouldPrintAVectorOfStrings) { + ostringstream os; + vector value = vector({"asdf", "fdsa", "lemon", "cherry"}); + os << value; + // Ideally "[ \"asdf\", \"fdsa\", \"lemon\", \"cherry\" ]" + EXPECT_THAT(os.str(), Eq("[ \"asdf\", \"fdsa\", \"lemon\", \"cherry\" ]")); +} + +TEST(VectorCompare, ShouldPrintSizeMismatch) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({1, 2, 3}); + Compare(os, first, second); + EXPECT_THAT(os.str(), Eq("size mismatch expected: 4, actual: 3")); +} + +TEST(VectorCompare, ShouldPrintVectorsDifferAtIndexZero) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({0, 1, 2, 3}); + Compare(os, first, second); + EXPECT_THAT( + os.str(), + Eq("vectors differ at index 0, \"1\" != \"0\", expected: \"[ 1, 2, 3, 4 ]\", actual: \"[ 0, 1, 2, 3 ]\"")); +} + +TEST(VectorCompare, ShouldPrintVectorsDifferAtEnd) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({1, 2, 3, 0}); + Compare(os, first, second); + EXPECT_THAT( + os.str(), + Eq("vectors differ at index 3, \"4\" != \"0\", expected: \"[ 1, 2, 3, 4 ]\", actual: \"[ 1, 2, 3, 0 ]\"")); +} + +TEST(VectorCompare, ShouldPrintNothingWhenVectorsAreEqual) { + ostringstream os; + vector first = vector({1, 2, 3, 4}); + vector second = vector({1, 2, 3, 4}); + Compare(os, first, second); + EXPECT_THAT(os.str(), Eq("")); +} + +TEST(TestResults, ShouldConstructTheDefaultInstance) { + TestResults actual; + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(0)); +} + +TEST(TestResults, ShouldCopyAnotherInstance) { + TestResults original; + original.Pass().Skip().Skip().Fail().Fail().Fail().Error().Error().Error().Error(); + TestResults actual(original); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(4)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(3)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(2)); + EXPECT_THAT(actual.Passed(), Eq(1)); + EXPECT_THAT(actual.Total(), Eq(6)); +} + +TEST(TestResults, ShouldCreateASpecificInstance) { + TestResults actual(1, 2, 3, 4, 5, {"hello"}, {"first", "second"}, {"third", "fourth", "fifth"}); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(1)); + EXPECT_THAT(actual.Errors(), Eq(1)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(2)); + EXPECT_THAT(actual.Failed(), Eq(2)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(3)); + EXPECT_THAT(actual.Skipped(), Eq(4)); + EXPECT_THAT(actual.Passed(), Eq(3)); + EXPECT_THAT(actual.Total(), Eq(5)); +} + +TEST(TestResults, ShouldReportAnErrorWithoutAMessage) { + TestResults actual; + actual.Error(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(1)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(0)); +} + +TEST(TestResults, ShouldReportAnErrorWithAMessage) { + TestResults actual; + actual.Error("my error message"); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(1)); + EXPECT_THAT(actual.ErrorMessages()[0], Eq("my error message")); + EXPECT_THAT(actual.Errors(), Eq(1)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(0)); +} + +TEST(TestResults, ShouldReportAFailureWithoutAMessage) { + TestResults actual; + actual.Fail(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(1)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldReportAFailureWithAMessage) { + TestResults actual; + actual.Fail("this test failed"); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(1)); + EXPECT_THAT(actual.FailureMessages()[0], Eq("this test failed")); + EXPECT_THAT(actual.Failed(), Eq(1)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +// You can't currently pass with a message. +TEST(TestResults, ShouldReportAPassWithoutAMessage) { + TestResults actual; + actual.Pass(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(0)); + EXPECT_THAT(actual.Passed(), Eq(1)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldReportASkipWithoutAMessage) { + TestResults actual; + actual.Skip(); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(1)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldReportASkipWithAMessage) { + TestResults actual; + actual.Skip("not ready yet"); + EXPECT_THAT(actual.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(actual.Errors(), Eq(0)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(0)); + EXPECT_THAT(actual.Failed(), Eq(0)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(1)); + EXPECT_THAT(actual.SkipMessages()[0], Eq("not ready yet")); + EXPECT_THAT(actual.Skipped(), Eq(1)); + EXPECT_THAT(actual.Passed(), Eq(0)); + EXPECT_THAT(actual.Total(), Eq(1)); +} + +TEST(TestResults, ShouldGetACombinationOfTwoInstances) { + const TestResults first = TestResults().Pass().Skip("S").Fail("F"); + const TestResults second = TestResults().Skip().Error("A").Error("B").Fail("D"); + const TestResults third = first + second; + + // Expect first was unchanged. + EXPECT_THAT(first.ErrorMessages().size(), Eq(0)); + EXPECT_THAT(first.Errors(), Eq(0)); + EXPECT_THAT(first.FailureMessages().size(), Eq(1)); + EXPECT_THAT(first.FailureMessages()[0], Eq("F")); + EXPECT_THAT(first.Failed(), Eq(1)); + EXPECT_THAT(first.SkipMessages().size(), Eq(1)); + EXPECT_THAT(first.SkipMessages()[0], Eq("S")); + EXPECT_THAT(first.Skipped(), Eq(1)); + EXPECT_THAT(first.Passed(), Eq(1)); + EXPECT_THAT(first.Total(), Eq(3)); + + // Expect second was unchanged + EXPECT_THAT(second.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(second.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(second.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(second.Errors(), Eq(2)); + EXPECT_THAT(second.FailureMessages().size(), Eq(1)); + EXPECT_THAT(second.FailureMessages()[0], Eq("D")); + EXPECT_THAT(second.Failed(), Eq(1)); + EXPECT_THAT(second.SkipMessages().size(), Eq(0)); + EXPECT_THAT(second.Skipped(), Eq(1)); + EXPECT_THAT(second.Passed(), Eq(0)); + EXPECT_THAT(second.Total(), Eq(2)); + + // Expect third was the combination of the two. + EXPECT_THAT(third.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(third.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(third.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(third.Errors(), Eq(2)); + EXPECT_THAT(third.FailureMessages().size(), Eq(2)); + EXPECT_THAT(third.FailureMessages()[0], Eq("F")); + EXPECT_THAT(third.FailureMessages()[1], Eq("D")); + EXPECT_THAT(third.Failed(), Eq(2)); + EXPECT_THAT(third.SkipMessages().size(), Eq(1)); + EXPECT_THAT(third.SkipMessages()[0], Eq("S")); + EXPECT_THAT(third.Skipped(), Eq(2)); + EXPECT_THAT(third.Passed(), Eq(1)); + EXPECT_THAT(third.Total(), Eq(5)); +} + +TEST(TestResults, ShouldCombineAnotherInstanceIntoThisOne) { + TestResults first = TestResults().Pass().Skip("S").Fail("F"); + const TestResults second = TestResults().Skip().Error("A").Error("B").Fail("D"); + first += second; + + // Expect second was unchanged + EXPECT_THAT(second.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(second.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(second.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(second.Errors(), Eq(2)); + EXPECT_THAT(second.FailureMessages().size(), Eq(1)); + EXPECT_THAT(second.FailureMessages()[0], Eq("D")); + EXPECT_THAT(second.Failed(), Eq(1)); + EXPECT_THAT(second.SkipMessages().size(), Eq(0)); + EXPECT_THAT(second.Skipped(), Eq(1)); + EXPECT_THAT(second.Passed(), Eq(0)); + EXPECT_THAT(second.Total(), Eq(2)); + + // Expect first is the combination of the two. + EXPECT_THAT(first.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(first.ErrorMessages()[0], Eq("A")); + EXPECT_THAT(first.ErrorMessages()[1], Eq("B")); + EXPECT_THAT(first.Errors(), Eq(2)); + EXPECT_THAT(first.FailureMessages().size(), Eq(2)); + EXPECT_THAT(first.FailureMessages()[0], Eq("F")); + EXPECT_THAT(first.FailureMessages()[1], Eq("D")); + EXPECT_THAT(first.Failed(), Eq(2)); + EXPECT_THAT(first.SkipMessages().size(), Eq(1)); + EXPECT_THAT(first.SkipMessages()[0], Eq("S")); + EXPECT_THAT(first.Skipped(), Eq(2)); + EXPECT_THAT(first.Passed(), Eq(1)); + EXPECT_THAT(first.Total(), Eq(5)); +} + +TEST(TestResults, ShouldCombineAnInstanceWithItself) { + TestResults actual = TestResults().Pass().Fail("A").Fail("B").Skip().Error("Bad").Skip(); + actual += actual; + EXPECT_THAT(actual.ErrorMessages().size(), Eq(2)); + EXPECT_THAT(actual.ErrorMessages().at(0), Eq("Bad")); + EXPECT_THAT(actual.ErrorMessages().at(1), Eq("Bad")); + EXPECT_THAT(actual.Errors(), Eq(2)); + EXPECT_THAT(actual.FailureMessages().size(), Eq(4)); + EXPECT_THAT(actual.FailureMessages().at(0), Eq("A")); + EXPECT_THAT(actual.FailureMessages().at(1), Eq("B")); + EXPECT_THAT(actual.FailureMessages().at(2), Eq("A")); + EXPECT_THAT(actual.FailureMessages().at(3), Eq("B")); + EXPECT_THAT(actual.Failed(), Eq(4)); + EXPECT_THAT(actual.SkipMessages().size(), Eq(0)); + EXPECT_THAT(actual.Skipped(), Eq(4)); + EXPECT_THAT(actual.Passed(), Eq(2)); + EXPECT_THAT(actual.Total(), Eq(10)); +} + +TEST(DefaultTestCompareFunction, ShouldBeNullOpt) { + auto actual = DefaultTestCompareFunction(); + EXPECT_THAT(actual, Eq(nullopt)); +} + +TEST(DefaultTestConfigureFunction, ShouldBeNullOpt) { + auto actual = DefaultTestConfigureFunction(); + EXPECT_THAT(actual, Eq(nullopt)); +} + +// Test that TestTuple makes the right kind of tuple. +TEST(TestTuple, ShouldDoTheThing) { + TestTuple test = { + (string) "ASDF", + 0, + {(string) "first", 2, "third"}, + (MaybeTestCompareFunction)nullopt, + (MaybeTestConfigureFunction)nullopt, + (MaybeTestConfigureFunction)nullopt, + true, + }; + tuple arguments = get<2>(test); + EXPECT_THAT(get<0>(test), Eq("ASDF")); + EXPECT_THAT(get<1>(test), Eq(0)); + EXPECT_THAT(get<2>(test), Eq(make_tuple((string) "first", 2, "third"))); + EXPECT_THAT(get<3>(test), Eq(nullopt)); + EXPECT_THAT(get<4>(test), Eq(nullopt)); + EXPECT_THAT(get<5>(test), Eq(nullopt)); + EXPECT_THAT(get<6>(test), Eq(true)); +} + +TEST(TestTuple, ShouldCoerceValuesToTheCorrectTypes) { + TestTuple test = { + "A", + 0, + {"B", 1, "C"}, + nullopt, + nullopt, + nullopt, + true, + }; + tuple arguments = get<2>(test); + EXPECT_THAT(get<0>(test), Eq((string) "A")); + EXPECT_THAT(get<1>(test), Eq(0)); + EXPECT_THAT(get<2>(test), Eq(make_tuple((string) "B", 1, "C"))); + EXPECT_THAT(get<3>(test), Eq(nullopt)); + EXPECT_THAT(get<4>(test), Eq(nullopt)); + EXPECT_THAT(get<5>(test), Eq(nullopt)); + EXPECT_THAT(get<6>(test), Eq(true)); +} + +TEST(MakeTest, ShouldMakeTests) { + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + + tuple first = MakeTest( + (string) "A Test", (string) "A", make_tuple((string) "ABCDEFG", 0), test_Compare, before_each, after_each, false); + + TestTuple second = + MakeTest("Another Test", "B", make_tuple((string) "ABCDEF", 1)); + TestTuple third = first; + + EXPECT_THAT(get<0>(first), Eq("A Test")); + EXPECT_THAT(get<0>(second), Eq("Another Test")); + EXPECT_THAT(get<0>(third), Eq("A Test")); + + EXPECT_THAT(get<1>(first), Eq("A")); + EXPECT_THAT(get<1>(second), Eq("B")); + EXPECT_THAT(get<1>(third), Eq("A")); + + EXPECT_THAT(get<2>(first), Eq(make_tuple((string) "ABCDEFG", 0))); + EXPECT_THAT(get<2>(second), Eq(make_tuple((string) "ABCDEF", 1))); + EXPECT_THAT(get<2>(third), Eq(make_tuple((string) "ABCDEFG", 0))); + + // TODO: We can only test Eq(nullopt) or not. + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<3>(second), Eq(nullopt)); + EXPECT_THAT(get<3>(third), Ne(nullopt)); + + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(second), Eq(nullopt)); + EXPECT_THAT(get<4>(third), Ne(nullopt)); + + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(second), Eq(nullopt)); + EXPECT_THAT(get<5>(third), Ne(nullopt)); + + EXPECT_THAT(get<6>(first), Eq(false)); + EXPECT_THAT(get<6>(second), Eq(true)); + EXPECT_THAT(get<6>(third), Eq(false)); + + // TODO: Create make_inputs and TestInputParams to reduce the amount of type casting in these lines. +} + +TEST(TestSuite, ShouldCoerceValuesToTheCorrectTypes) { + auto fnToTest = [](const string& text, int position) -> string { + if (position >= 0 && position < text.size()) { + return &text.at(position); + } + return ""; + }; + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestCompareFunction suite_Compare = [](const string& left, const string& right) -> bool { return true; }; + MaybeTestConfigureFunction after_all = []() {}; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_all = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + TestTuple test_run = MakeTest( + "Test Name", "Expected", make_tuple((string) "text", 0), test_Compare, before_each, after_each, false); + TestSuite first = { + "Suite Name", + fnToTest, + { + test_run, + }, + suite_Compare, + before_all, + after_all, + true, + }; + EXPECT_THAT(get<0>(first), Eq("Suite Name")); + // EXPECT_THAT(get<1>(first), Eq(fnToTest)); + EXPECT_THAT(get<2>(first).size(), Eq(1)); + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<6>(first), Eq(true)); + + auto test_data = *get<2>(first).begin(); + EXPECT_THAT(get<0>(test_data), Eq("Test Name")); + EXPECT_THAT(get<1>(test_data), Eq("Expected")); + // Item 2 is checked below as inputs. + EXPECT_THAT(get<3>(test_data), Ne(nullopt)); + EXPECT_THAT(get<4>(test_data), Ne(nullopt)); + EXPECT_THAT(get<5>(test_data), Ne(nullopt)); + EXPECT_THAT(get<6>(test_data), Eq(false)); + + auto inputs = get<2>(test_data); + EXPECT_THAT(get<0>(inputs), Eq("text")); + EXPECT_THAT(get<1>(inputs), Eq(0)); +} + +TEST(MakeTestSuite, ShouldMakeATestSuiteWithAVectorOfTestRuns) { + auto fnToTest = [](const string& text, int position) -> string { + if (position >= 0 && position < text.size()) { + return &text.at(position); + } + return ""; + }; + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestCompareFunction suite_Compare = [](const string& left, const string& right) -> bool { return true; }; + MaybeTestConfigureFunction after_all = []() {}; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_all = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + TestTuple test_run = MakeTest( + "Test Name", "Expected", make_tuple((string) "text", 0), test_Compare, before_each, after_each, false); + TestSuite first = + MakeTestSuite("Suite Name", fnToTest, {test_run}, suite_Compare, before_all, after_all, false); + + EXPECT_THAT(get<0>(first), Eq("Suite Name")); + // EXPECT_THAT(get<1>(first), Eq(fnToTest)); + EXPECT_THAT(get<2>(first).size(), Eq(1)); + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<6>(first), Eq(false)); + + auto test_data = *get<2>(first).begin(); + EXPECT_THAT(get<0>(test_data), Eq("Test Name")); + EXPECT_THAT(get<1>(test_data), Eq("Expected")); + // Item 2 is checked below as inputs. + EXPECT_THAT(get<3>(test_data), Ne(nullopt)); + EXPECT_THAT(get<4>(test_data), Ne(nullopt)); + EXPECT_THAT(get<5>(test_data), Ne(nullopt)); + EXPECT_THAT(get<6>(test_data), Eq(false)); + + auto inputs = get<2>(test_data); + EXPECT_THAT(get<0>(inputs), Eq("text")); + EXPECT_THAT(get<1>(inputs), Eq(0)); +} + +TEST(MakeTestSuite, ShouldMakeATestSuiteWithAnInitializerListOfTestRuns) { + auto fnToTest = [](const string& text, int position) -> string { + if (position >= 0 && position < text.size()) { + return &text.at(position); + } + return ""; + }; + MaybeTestCompareFunction test_Compare = [](const string& left, const string& right) -> bool { return false; }; + MaybeTestCompareFunction suite_Compare = [](const string& left, const string& right) -> bool { return true; }; + MaybeTestConfigureFunction after_all = []() {}; + MaybeTestConfigureFunction after_each = []() {}; + MaybeTestConfigureFunction before_all = []() {}; + MaybeTestConfigureFunction before_each = []() {}; + TestTuple test_run = MakeTest( + "Test Name", "Expected", make_tuple((string) "text", 0), test_Compare, before_each, after_each, false); + TestSuite first = + MakeTestSuite("Suite Two", fnToTest, {test_run}, suite_Compare, before_all, after_all, true); + + EXPECT_THAT(get<0>(first), Eq("Suite Two")); + // EXPECT_THAT(get<1>(first), Eq(fnToTest)); + EXPECT_THAT(get<2>(first).size(), Eq(1)); + EXPECT_THAT(get<3>(first), Ne(nullopt)); + EXPECT_THAT(get<4>(first), Ne(nullopt)); + EXPECT_THAT(get<5>(first), Ne(nullopt)); + EXPECT_THAT(get<6>(first), Eq(true)); + + auto test_data = *get<2>(first).begin(); + EXPECT_THAT(get<0>(test_data), Eq("Test Name")); + EXPECT_THAT(get<1>(test_data), Eq("Expected")); + // Item 2 is checked below as inputs. + EXPECT_THAT(get<3>(test_data), Ne(nullopt)); + EXPECT_THAT(get<4>(test_data), Ne(nullopt)); + EXPECT_THAT(get<5>(test_data), Ne(nullopt)); + EXPECT_THAT(get<6>(test_data), Eq(false)); + + auto inputs = get<2>(test_data); + EXPECT_THAT(get<0>(inputs), Eq("text")); + EXPECT_THAT(get<1>(inputs), Eq(0)); +} + +TEST(PrintResults, ShouldDoTheThing) { + TestResults results; + results.Error() + .Fail() + .Fail() + .Skip() + .Skip() + .Skip() + .Pass() + .Pass() + .Pass() + .Pass() + .Pass() + .Skip("skip with a message") + .Fail("fail with a message") + .Error("error with a message"); + ostringstream os; + PrintResults(os, results); + EXPECT_THAT(os.str(), Eq(R"test(Skipped: +🚧Skipped: skip with a message +Failures: +❌FAILED: fail with a message +Errors: +🔥ERROR: error with a message +Total tests: 12 +Passed: 5 ✅ +Failed: 3 ❌ +Skipped: 4 🚧 +Errors: 2 🔥 +)test")); +} + +TEST(Coalesce, ShouldCombineTwoNulls) { + MaybeTestConfigureFunction fn1 = nullopt; + MaybeTestConfigureFunction fn2 = nullopt; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Eq(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(false)); +} + +TEST(Coalesce, ShouldCombineAFunctionWithANull) { + bool this_starts_false; + MaybeTestConfigureFunction fn1 = [&this_starts_false]() { this_starts_false = true; }; + MaybeTestConfigureFunction fn2 = nullopt; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(this_starts_false, true); +} + +TEST(Coalesce, ShouldCombineANullWithAFunction) { + bool this_starts_false; + MaybeTestConfigureFunction fn1 = nullopt; + MaybeTestConfigureFunction fn2 = [&this_starts_false]() { this_starts_false = true; }; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(this_starts_false, true); +} + +TEST(Coalesce, ShouldCombineTwoFunctions) { + bool red_flag = false; + bool blue_flag = false; + MaybeTestConfigureFunction fn1 = [&red_flag]() { red_flag = true; }; + MaybeTestConfigureFunction fn2 = [&blue_flag]() { blue_flag = true; }; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(red_flag, Eq(true)); + EXPECT_THAT(blue_flag, Eq(true)); +} + +TEST(Coalesce, ShouldExecuteInTheCorrectOrder) { + vector lines; + MaybeTestConfigureFunction fn1 = [&lines]() { lines.push_back("Line 1"); }; + MaybeTestConfigureFunction fn2 = [&lines]() { lines.push_back("Line 2"); }; + MaybeTestConfigureFunction actual = Coalesce(fn1, fn2); + EXPECT_THAT(actual, Ne(nullopt)); + EXPECT_THAT(actual.has_value(), Eq(true)); + actual.value()(); + EXPECT_THAT(lines.size(), Eq(2)); + EXPECT_THAT(lines.at(0), Eq("Line 1")); + EXPECT_THAT(lines.at(1), Eq("Line 2")); +} + +TEST(ExecuteSuiteWithParams, ShouldNotExecuteADisabledSuite) { + bool suite_Compare_called = false; + MaybeTestCompareFunction suite_Compare = [&suite_Compare_called](bool left, bool right) { + suite_Compare_called = true; + return left == right; + }; + bool before_all_called = false; + MaybeTestConfigureFunction before_all = [&before_all_called]() { before_all_called = true; }; + bool after_all_called = false; + MaybeTestConfigureFunction after_all = [&after_all_called]() { after_all_called = true; }; + bool test_Compare_called = false; + MaybeTestCompareFunction test_Compare = [&test_Compare_called](bool left, bool right) { + test_Compare_called = true; + return left == right; + }; + bool before_each_called = false; + MaybeTestConfigureFunction before_each = [&before_each_called]() { before_each_called = true; }; + bool after_each_called = false; + MaybeTestConfigureFunction after_each = [&after_each_called]() { after_each_called = true; }; + bool test_function_called = false; + function test_function = [&test_function_called]() { + test_function_called = true; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + false); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, Eq(R"test(🚧Skipping suite: My Suite because it is disabled. + 🚧Skipping Test: Test Name because the suite is disabled. +)test")); + EXPECT_THAT(test_function_called, Eq(false)); + EXPECT_THAT(suite_Compare_called, Eq(false)); + EXPECT_THAT(before_all_called, Eq(false)); + EXPECT_THAT(after_all_called, Eq(false)); + EXPECT_THAT(test_Compare_called, Eq(false)); + EXPECT_THAT(before_each_called, Eq(false)); + EXPECT_THAT(after_each_called, Eq(false)); +} + +TEST(ExecuteSuiteWithParams, ShouldNotExecuteASuiteWithNoTests) { + bool suite_Compare_called = false; + MaybeTestCompareFunction suite_Compare = [&suite_Compare_called](bool left, bool right) { + suite_Compare_called = true; + return left == right; + }; + bool before_all_called = false; + MaybeTestConfigureFunction before_all = [&before_all_called]() { before_all_called = true; }; + bool after_all_called = false; + MaybeTestConfigureFunction after_all = [&after_all_called]() { after_all_called = true; }; + bool test_Compare_called = false; + MaybeTestCompareFunction test_Compare = [&test_Compare_called](bool left, bool right) { + test_Compare_called = true; + return left == right; + }; + bool before_each_called = false; + MaybeTestConfigureFunction before_each = [&before_each_called]() { before_each_called = true; }; + bool after_each_called = false; + MaybeTestConfigureFunction after_each = [&after_each_called]() { after_each_called = true; }; + bool test_function_called = false; + function test_function = [&test_function_called]() { + test_function_called = true; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", test_function, {}, suite_Compare, before_all, after_all, true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, Eq("🚧Skipping suite: My Suite because it is empty.\n")); + EXPECT_THAT(test_function_called, Eq(false)); + EXPECT_THAT(suite_Compare_called, Eq(false)); + EXPECT_THAT(before_all_called, Eq(false)); + EXPECT_THAT(after_all_called, Eq(false)); + EXPECT_THAT(test_Compare_called, Eq(false)); + EXPECT_THAT(before_each_called, Eq(false)); + EXPECT_THAT(after_each_called, Eq(false)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASinglePass) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + ✅PASSED + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASingleFailure) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", false, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + ❌FAILED: expected: "0", actual: "1" + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASingleSkip) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, false), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + 🚧Skipping Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(0)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(0)); + EXPECT_THAT(before_each_call_count, Eq(0)); + EXPECT_THAT(after_each_call_count, Eq(0)); +} + +TEST(ExecuteSuiteWithParams, ShouldExecuteASuiteWithASinglePassAndADisabledTest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + MakeTest("Second Test", false, make_tuple(), test_Compare, before_each, after_each, false), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + ✅PASSED + Ending Test: Test Name + 🚧Skipping Test: Second Test +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchAnExceptionThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw(std::exception()); + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught exception "std::exception". + ❌FAILED: expected: "1", actual: "0" + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchAStringThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw((string) "burp"); + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught string "burp". + ❌FAILED: expected: "1", actual: "0" + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchACStringThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw "burp"; + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught c-string "burp". + ❌FAILED: expected: "1", actual: "0" + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithParams, ShouldCatchSomethingElseThrownByATest) { + int suite_Compare_call_count = 0; + MaybeTestCompareFunction suite_Compare = [&](bool left, bool right) { + suite_Compare_call_count++; + return left == right; + }; + int before_all_call_count = 0; + MaybeTestConfigureFunction before_all = [&]() { before_all_call_count++; }; + int after_all_call_count = 0; + MaybeTestConfigureFunction after_all = [&]() { after_all_call_count++; }; + int test_Compare_call_count = 0; + MaybeTestCompareFunction test_Compare = [&test_Compare_call_count](bool left, bool right) { + test_Compare_call_count++; + return left == right; + }; + int before_each_call_count = 0; + MaybeTestConfigureFunction before_each = [&before_each_call_count]() { before_each_call_count++; }; + int after_each_call_count = 0; + MaybeTestConfigureFunction after_each = [&after_each_call_count]() { after_each_call_count++; }; + int test_function_call_count = 0; + function test_function = [&test_function_call_count]() { + test_function_call_count++; + throw(42); + return true; + }; + + // TODO: Remove this wrapper function once InterceptCout works properly with parameters. + function wrapper = [&]() { + ExecuteSuite("My Suite", + test_function, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + true); + }; + + string output = InterceptCout(wrapper); + // string output = ""; + // EXPECT_THROW((output = InterceptCout(wrapper)), std::exception); + EXPECT_THAT(output, + Eq( + R"test(🚀Beginning Suite: My Suite + Beginning Test: Test Name + 🔥ERROR: Caught something that is neither an std::exception nor an std::string. + ❌FAILED: expected: "1", actual: "0" + Ending Test: Test Name +Ending Suite: My Suite +)test")); + EXPECT_THAT(test_function_call_count, Eq(1)); + EXPECT_THAT(suite_Compare_call_count, Eq(0)); + EXPECT_THAT(before_all_call_count, Eq(1)); + EXPECT_THAT(after_all_call_count, Eq(1)); + EXPECT_THAT(test_Compare_call_count, Eq(1)); + EXPECT_THAT(before_each_call_count, Eq(1)); + EXPECT_THAT(after_each_call_count, Eq(1)); +} + +TEST(ExecuteSuiteWithTuple, ShouldNotExecuteADisabledSuite) { + bool suite_Compare_called = false; + MaybeTestCompareFunction suite_Compare = [&suite_Compare_called](bool left, bool right) { + suite_Compare_called = true; + return left == right; + }; + bool before_all_called = false; + MaybeTestConfigureFunction before_all = [&before_all_called]() { before_all_called = true; }; + bool after_all_called = false; + MaybeTestConfigureFunction after_all = [&after_all_called]() { after_all_called = true; }; + bool test_Compare_called = false; + MaybeTestCompareFunction test_Compare = [&test_Compare_called](bool left, bool right) { + test_Compare_called = true; + return left == right; + }; + bool before_each_called = false; + MaybeTestConfigureFunction before_each = [&before_each_called]() { before_each_called = true; }; + bool after_each_called = false; + MaybeTestConfigureFunction after_each = [&after_each_called]() { after_each_called = true; }; + bool test_function_called = false; + function test_function = [&test_function_called]() { + test_function_called = true; + return true; + }; + TestSuite test_suite = MakeTestSuite( + "My Suite", + []() { return true; }, + { + MakeTest("Test Name", true, make_tuple(), test_Compare, before_each, after_each, true), + }, + suite_Compare, + before_all, + after_all, + false); + function wrapper = [&test_suite]() { ExecuteSuite(test_suite); }; + + string output = InterceptCout(wrapper); + EXPECT_THAT(output, + Eq("🚧Skipping suite: My Suite because it is disabled.\n 🚧Skipping Test: Test Name because the suite " + "is disabled.\n")); + EXPECT_THAT(test_function_called, Eq(false)); + EXPECT_THAT(suite_Compare_called, Eq(false)); + EXPECT_THAT(before_all_called, Eq(false)); + EXPECT_THAT(after_all_called, Eq(false)); + EXPECT_THAT(test_Compare_called, Eq(false)); + EXPECT_THAT(before_each_called, Eq(false)); + EXPECT_THAT(after_each_called, Eq(false)); +} + +// TODO: Add tests for ExecuteSuite with tuple. +/* +For each ExecuteSuite variant. +- Should not execute a disabled suite. +* Should execute a suite with no tests. +* Should execute a suite with a single pass. +* Should execute a suite with a single fail. +* Should execute a suite with a single skip/disabled test. +* Should execute a suite with a pass and a disabled test. +* Should catch an exception thrown by a test and record it as an error. +* Should catch a string thrown by a test and record it as an error. +* Should catch something else thrown by a test and record it as an error. +For all tests +* Should call before_all once before the first before_each. +* Should call before_each once per test. +* Should call after_each once per test. +* Should call after_all once after the last after_each. +* Should print output. Test with one of each kind of test above to make sure all cout statements happen correctly. +Hijack cout before calling ExecuteSuite and restore it after. Split the recorded cout output into lines for easier +order checking while allowing for parallelism. +*/ +// TODO: Test InterceptCout. +// TODO: Test container printer prints initializer_lists and other non-vector containers. +// TODO: Test SkipTest +} // End namespace