From b16ff123343e2a472ed4265468cb4a2ed052094b Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 08:03:58 -0400 Subject: [PATCH 01/42] snake_case tester --- test/driver.cpp | 4 +-- test/options.cpp | 10 +++---- test/tester.cpp | 74 ++++++++++++++++++++++++------------------------ test/tester.h | 16 +++++------ 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 63e6346..61396aa 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -65,9 +65,9 @@ int main(int argc, char* argv[]) } if (options.summary) - summarizeTests(); + summarize_tests(); - return getTestsFailed(); + return get_tests_failed(); } diff --git a/test/options.cpp b/test/options.cpp index 3bf23c3..93bdbd2 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -116,16 +116,16 @@ Options get_options(char* arguments[], const std::size_t size) void apply_options(const Options& options, std::ofstream& fileOut) { - setHeaderText(options.header_text); - setPassReportMode(options.prm); - setFailThreshold(options.fail_threshold); + set_header_text(options.header_text); + set_pass_report_mode(options.prm); + set_fail_threshold(options.fail_threshold); //if output to file option not enabled, use standard output //else open output file in appropriate mode if (options.fom == file_open_mode::no_file) - setOutput(std::cout); + set_output(std::cout); else { - setOutput(fileOut); + set_output(fileOut); //enforce create-only file open mode if (options.fom == file_open_mode::new_file && std::filesystem::exists(options.output_filepath)) { diff --git a/test/tester.cpp b/test/tester.cpp index 0fd8c28..0930144 100644 --- a/test/tester.cpp +++ b/test/tester.cpp @@ -19,85 +19,85 @@ #include "tester.h" static std::string headerText("Running tests"); -void setHeaderText(std::string text) +void set_header_text(std::string text) { headerText = text; } -static pass_report_mode passMode{ pass_report_mode::indicate }; -void setPassReportMode(pass_report_mode mode) +static pass_report_mode prm{ pass_report_mode::indicate }; +void set_pass_report_mode(pass_report_mode mode) { - passMode = mode; + prm = mode; } -static unsigned short failThreshold{ 0 }; -void setFailThreshold(unsigned short value) +static unsigned short fail_threshold{ 0 }; +void set_fail_threshold(unsigned short value) { - failThreshold = value; + fail_threshold = value; } -void setMaxFailThreshold() +void set_max_fail_threshold() { - failThreshold = USHRT_MAX; + fail_threshold = USHRT_MAX; } static std::ostream* pOut{ &std::cout }; -void setOutput(std::ostream& o) +void set_output(std::ostream& o) { pOut = &o; } //number of tests run and failed -static unsigned testsDone; -unsigned getTestsDone() +static unsigned tests_done; +unsigned get_tests_done() { - return testsDone; + return tests_done; } -static unsigned testsFailed; -unsigned getTestsFailed() +static unsigned tests_failed; +unsigned get_tests_failed() { - return testsFailed; + return tests_failed; } -bool lastOutputEndedInLineBreak{ false }; +bool last_output_ended_in_linebreak{ false }; //track number of tests and check test result void verify(bool success, const char* hint) { - ++testsDone; - if (testsDone == 1 && !headerText.empty()) + ++tests_done; + if (tests_done == 1 && !headerText.empty()) *pOut << headerText << ":\n"; std::ostringstream message; //assume stream is at start of line on first call - lastOutputEndedInLineBreak = testsDone == 1; + last_output_ended_in_linebreak = tests_done == 1; if (success) { - if (passMode == pass_report_mode::indicate) + if (prm == pass_report_mode::indicate) message << '.'; - else if (passMode == pass_report_mode::detail) { - message << "Test# " << testsDone << ": Pass (" << hint << ")\n"; - lastOutputEndedInLineBreak = true; + else if (prm == pass_report_mode::detail) { + message << "Test# " << tests_done << ": Pass (" << hint << ")\n"; + last_output_ended_in_linebreak = true; } } else { - ++testsFailed; + ++tests_failed; - if (!lastOutputEndedInLineBreak) + if (!last_output_ended_in_linebreak) message << '\n'; - message << "Test# " << testsDone << ": FAIL (" << hint << ")\n"; - lastOutputEndedInLineBreak = true; + message << "Test# " << tests_done << ": FAIL (" << hint << ")\n"; + last_output_ended_in_linebreak = true; - if (testsFailed > failThreshold) + if (tests_failed > fail_threshold) throw message.str(); } @@ -106,22 +106,22 @@ void verify(bool success, const char* hint) //print a simple test report -void summarizeTests() +void summarize_tests() { //TODO: there should always be one and exactly one empty line before summary //-assume if an exception was thrown earlier on test failure, the client //-printed the msg and caused a line break after printing the msg - if (!lastOutputEndedInLineBreak) + if (!last_output_ended_in_linebreak) *pOut << '\n'; - else if (testsFailed <= failThreshold) + else if (tests_failed <= fail_threshold) *pOut << '\n'; - *pOut << "Tests completed: " << testsDone << '\n'; - *pOut << "Tests passed: " << testsDone - testsFailed << '\n'; - *pOut << "Tests failed: " << testsFailed << '\n'; + *pOut << "Tests completed: " << tests_done << '\n'; + *pOut << "Tests passed: " << tests_done - tests_failed << '\n'; + *pOut << "Tests failed: " << tests_failed << '\n'; - if (testsFailed > failThreshold) - *pOut << "Tests stopped after " << testsFailed << " failure(s)\n"; + if (tests_failed > fail_threshold) + *pOut << "Tests stopped after " << tests_failed << " failure(s)\n"; } diff --git a/test/tester.h b/test/tester.h index f736d9f..40635a8 100644 --- a/test/tester.h +++ b/test/tester.h @@ -18,17 +18,17 @@ enum class pass_report_mode { none, indicate, detail }; -void setHeaderText(std::string text); -void setPassReportMode(pass_report_mode mode); -void setFailThreshold(unsigned short value); -void setMaxFailThreshold(); -void setOutput(std::ostream& o); +void set_header_text(std::string text); +void set_pass_report_mode(pass_report_mode mode); +void set_fail_threshold(unsigned short value); +void set_max_fail_threshold(); +void set_output(std::ostream& o); -unsigned getTestsDone(); -unsigned getTestsFailed(); +unsigned get_tests_done(); +unsigned get_tests_failed(); void verify(bool success, const char* msg); -void summarizeTests(); +void summarize_tests(); void log(const char* s); void logLine(const char* s); From 0441166aa0fffbb1e6504f62e75898e0c215e9f6 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 08:04:56 -0400 Subject: [PATCH 02/42] snake_case logLine --- test/driver.cpp | 2 +- test/tester.cpp | 2 +- test/tester.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 61396aa..83cd9fd 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) runTests(); } catch (const std::string& msg) { - logLine(msg.data()); + log_line(msg.data()); if (options.fom == file_open_mode::no_file) show_error(msg.data()); } diff --git a/test/tester.cpp b/test/tester.cpp index 0930144..2b83652 100644 --- a/test/tester.cpp +++ b/test/tester.cpp @@ -131,7 +131,7 @@ void log(const char* s) } -void logLine(const char* s) +void log_line(const char* s) { *pOut << s << '\n'; } diff --git a/test/tester.h b/test/tester.h index 40635a8..44267f0 100644 --- a/test/tester.h +++ b/test/tester.h @@ -31,6 +31,6 @@ void verify(bool success, const char* msg); void summarize_tests(); void log(const char* s); -void logLine(const char* s); +void log_line(const char* s); #endif \ No newline at end of file From 9240223ba1e1d9ced79725c97cda39025857f1bc Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 08:30:20 -0400 Subject: [PATCH 03/42] Helper fn templates to verify: more needed --- test/tester.h | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/test/tester.h b/test/tester.h index 44267f0..c6c38d4 100644 --- a/test/tester.h +++ b/test/tester.h @@ -15,6 +15,7 @@ #define STL_LITE_TESTER_H #include +#include enum class pass_report_mode { none, indicate, detail }; @@ -27,10 +28,71 @@ void set_output(std::ostream& o); unsigned get_tests_done(); unsigned get_tests_failed(); -void verify(bool success, const char* msg); void summarize_tests(); void log(const char* s); void log_line(const char* s); +void verify(bool success, const char* msg); + +// the following functions and templates are helper functions short-hand to verify +// call the helpers instead of calling verify directly + +inline void is_true(bool value, const char* msg) +{ + verify(value, msg); +} + + +inline void is_false(bool value, const char* msg) +{ + verify(!value, msg); +} + + +//TODO replace this template with a macro to reduce template instantiations +template +constexpr bool is_nonbool_arithmetic() +{ + return std::is_integral_v && !std::is_same_v; +} + + +template +inline void is_zero(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value == 0, msg); +} + +template +inline void is_nonzero(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value != 0, msg); +} + +template +inline void is_negative(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value < 0, msg); +} + + +template +inline void is_nonnegative(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value >= 0, msg); +} + + +template +inline void is_positive(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value > 0, msg); +} + #endif \ No newline at end of file From 2267e153934bf62f48ff9cfe82b53b6aa3a7995a Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 08:30:43 -0400 Subject: [PATCH 04/42] Replace verify with is_true helper --- test/array-test.cpp | 64 ++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/test/array-test.cpp b/test/array-test.cpp index 4090a4b..1b7f29b 100644 --- a/test/array-test.cpp +++ b/test/array-test.cpp @@ -30,41 +30,41 @@ void runTests() //capacity - verify(!s.empty(), "s.empty()"); - verify(s.size() == 3, "s.size()"); - verify(s.max_size() == s.size(), "s.max_size()"); + is_true(!s.empty(), "s.empty()"); + is_true(s.size() == 3, "s.size()"); + is_true(s.max_size() == s.size(), "s.max_size()"); - verify(!p.empty(), "p.empty()"); - verify(p.size() == 5, "p.size()"); - verify(p.max_size() == p.size(), "p.max_size()"); + is_true(!p.empty(), "p.empty()"); + is_true(p.size() == 5, "p.size()"); + is_true(p.max_size() == p.size(), "p.max_size()"); //element access - verify(s[0] == 8, "s[0]"); - verify(s[1] == -2, "s[1]"); - verify(s[2] == 7, "s[2]"); - verify(s[1] != 8, "s[1] != 8"); + is_true(s[0] == 8, "s[0]"); + is_true(s[1] == -2, "s[1]"); + is_true(s[2] == 7, "s[2]"); + is_true(s[1] != 8, "s[1] != 8"); - verify(p[0] == 8, "p[0]"); - verify(p[2] == 7, "p[2]"); - verify(p[4] == 0, "p[4]"); + is_true(p[0] == 8, "p[0]"); + is_true(p[2] == 7, "p[2]"); + is_true(p[4] == 0, "p[4]"); - verify(s.at(0) == 8, "s.at(0)"); - verify(s.at(1) == -2, "s.at(1)"); - verify(s.at(2) == 7, "s.at(2)"); + is_true(s.at(0) == 8, "s.at(0)"); + is_true(s.at(1) == -2, "s.at(1)"); + is_true(s.at(2) == 7, "s.at(2)"); - verify(p.at(0) == 8, "p.at(0)"); - verify(p.at(2) == 7, "p.at(2)"); - verify(p.at(4) == 0, "p.at(4)"); + is_true(p.at(0) == 8, "p.at(0)"); + is_true(p.at(2) == 7, "p.at(2)"); + is_true(p.at(4) == 0, "p.at(4)"); - verify(s.front() == 8, "s.front()"); - verify(s.front() != -2, "s.front() != -2"); - verify(s.back() == 7, "s.back()"); - verify(s.back() != -2, "s.back() != -2"); + is_true(s.front() == 8, "s.front()"); + is_true(s.front() != -2, "s.front() != -2"); + is_true(s.back() == 7, "s.back()"); + is_true(s.back() != -2, "s.back() != -2"); - verify(p.front() == 8, "p.front()"); - verify(p.front() != -2, "p.front() != -2"); - verify(p.back() == 0, "p.back()"); + is_true(p.front() == 8, "p.front()"); + is_true(p.front() != -2, "p.front() != -2"); + is_true(p.back() == 0, "p.back()"); //forward iterators @@ -75,7 +75,7 @@ void runTests() std::size_t i = 0; for (auto it = u.begin(); it != u.end() && iteratorTest; ++it, ++i) iteratorTest = *it == uExpected[i]; - verify(iteratorTest, "forward iterator"); + is_true(iteratorTest, "forward iterator"); //reverse iterators unsigned urExpected[] = { 6, 1, 3, 9, 5 }; @@ -84,17 +84,17 @@ void runTests() i = 0; for (auto it = u.rbegin(); it != u.rend() && iteratorTest; ++it, ++i) iteratorTest = *it == urExpected[i]; - verify(iteratorTest, "reverse iterator"); + is_true(iteratorTest, "reverse iterator"); //zero-size array array c; - verify(c.empty(), "c.empty()"); + is_true(c.empty(), "c.empty()"); //iterator on empty array: the loop body should not execute iteratorTest = true; for (const auto e : c) iteratorTest = false; - verify(iteratorTest, "fwd iterator on empty array"); + is_true(iteratorTest, "fwd iterator on empty array"); //fill @@ -106,7 +106,7 @@ void runTests() bool fillTest = !std::any_of(a.begin(), a.end(), [](char c) { return c != 'x'; } ); - verify(fillTest, "a.fill()"); + is_true(fillTest, "a.fill()"); //swap @@ -120,5 +120,5 @@ void runTests() bool swapTest = true; for (std::size_t idx = 0; idx < m.size() && swapTest; ++idx) swapTest = m[idx] == mExpected[idx] && n[idx] == nExpected[idx]; - verify(swapTest, "m.swap(n)"); + is_true(swapTest, "m.swap(n)"); } \ No newline at end of file From 382745a999f7fd666af5739f7b8e1ab3dbe90244 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 09:06:43 -0400 Subject: [PATCH 05/42] Support suite-level verification --- test/tester.cpp | 129 +++++++++++++++++++++++++++++++----------------- test/tester.h | 11 +++-- 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/test/tester.cpp b/test/tester.cpp index 2b83652..a19546a 100644 --- a/test/tester.cpp +++ b/test/tester.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "tester.h" @@ -52,60 +53,70 @@ void set_output(std::ostream& o) } -//number of tests run and failed -static unsigned tests_done; -unsigned get_tests_done() +void log(const char* s) { - return tests_done; + *pOut << s; } -static unsigned tests_failed; -unsigned get_tests_failed() +void log_line(const char* s) { - return tests_failed; + *pOut << s << '\n'; } -bool last_output_ended_in_linebreak{ false }; +static unsigned tests_done_total; +static unsigned tests_done_suite; +static unsigned tests_failed_total; +static unsigned tests_failed_suite; -//track number of tests and check test result -void verify(bool success, const char* hint) +unsigned get_tests_failed_total() { - ++tests_done; - if (tests_done == 1 && !headerText.empty()) - *pOut << headerText << ":\n"; + return tests_failed_total; +} - std::ostringstream message; - //assume stream is at start of line on first call - last_output_ended_in_linebreak = tests_done == 1; +static std::string suite_name; +static unsigned suites_run; +void start_suite(const std::string& name) +{ + assert(!name.empty()); + suite_name = name; + ++suites_run; - if (success) { - if (prm == pass_report_mode::indicate) - message << '.'; - else if (prm == pass_report_mode::detail) { - message << "Test# " << tests_done << ": Pass (" << hint << ")\n"; - last_output_ended_in_linebreak = true; - } - } - else { - ++tests_failed; + //print a separator between test suites + if (tests_done_suite != 0) + *pOut << "\n\n"; - if (!last_output_ended_in_linebreak) - message << '\n'; - message << "Test# " << tests_done << ": FAIL (" << hint << ")\n"; - last_output_ended_in_linebreak = true; + tests_done_suite = 0; + tests_failed_suite = 0; - if (tests_failed > fail_threshold) - throw message.str(); + if (!headerText.empty()) { + //TODO replace $suite with suite name before printing + *pOut << headerText << ":\n"; } +} - *pOut << message.str(); + +//helper for formatting output +bool last_output_ended_in_linebreak{ false }; + + +//print a report for the suite +void summarize_suite() +{ + if (!last_output_ended_in_linebreak) + *pOut << "\n\n"; + + *pOut << "Tests completed: " << tests_done_suite << '\n'; + *pOut << "Tests passed: " << tests_done_suite - tests_failed_suite << '\n'; + *pOut << "Tests failed: " << tests_failed_suite << '\n'; + + last_output_ended_in_linebreak = true; } -//print a simple test report +//print a report across all suites void summarize_tests() { //TODO: there should always be one and exactly one empty line before summary @@ -113,25 +124,51 @@ void summarize_tests() //-printed the msg and caused a line break after printing the msg if (!last_output_ended_in_linebreak) *pOut << '\n'; - else if (tests_failed <= fail_threshold) + else if (tests_failed_total <= fail_threshold) *pOut << '\n'; - *pOut << "Tests completed: " << tests_done << '\n'; - *pOut << "Tests passed: " << tests_done - tests_failed << '\n'; - *pOut << "Tests failed: " << tests_failed << '\n'; + *pOut << "Suites run: " << suites_run << '\n'; + *pOut << "Tests completed: " << tests_done_total << '\n'; + *pOut << "Tests passed: " << tests_done_total - tests_failed_total << '\n'; + *pOut << "Tests failed: " << tests_failed_total << '\n'; - if (tests_failed > fail_threshold) - *pOut << "Tests stopped after " << tests_failed << " failure(s)\n"; + if (tests_failed_total > fail_threshold) + *pOut << "Tests stopped after " << tests_failed_total << " failure(s)\n"; } -void log(const char* s) +//track number of tests and check test result +void verify(bool success, const char* hint) { - *pOut << s; -} + ++tests_done_total; + ++tests_done_suite; + //assume stream is at start of line on first call + last_output_ended_in_linebreak = tests_done_suite == 1; -void log_line(const char* s) -{ - *pOut << s << '\n'; + std::ostringstream message; + + + if (success) { + if (prm == pass_report_mode::indicate) + message << '.'; + else if (prm == pass_report_mode::detail) { + message << "Test# " << tests_done_suite << ": Pass (" << hint << ")\n"; + last_output_ended_in_linebreak = true; + } + } + else { + ++tests_failed_total; + ++tests_failed_suite; + + if (!last_output_ended_in_linebreak) + message << '\n'; + message << "Test# " << tests_done_suite << ": FAIL (" << hint << ")\n"; + last_output_ended_in_linebreak = true; + + if (tests_failed_total > fail_threshold) + throw message.str(); + } + + *pOut << message.str(); } diff --git a/test/tester.h b/test/tester.h index c6c38d4..010cedb 100644 --- a/test/tester.h +++ b/test/tester.h @@ -25,13 +25,16 @@ void set_fail_threshold(unsigned short value); void set_max_fail_threshold(); void set_output(std::ostream& o); +void log(const char* s); +void log_line(const char* s); + unsigned get_tests_done(); -unsigned get_tests_failed(); +unsigned get_tests_failed_total(); -void summarize_tests(); +void start_suite(const std::string& name); -void log(const char* s); -void log_line(const char* s); +void summarize_suite(); +void summarize_tests(); void verify(bool success, const char* msg); From 9304fb6d29c673ecee19730259b7fc0984483ed8 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 09:08:13 -0400 Subject: [PATCH 06/42] Initial support for multi-suite testing --- test/driver.cpp | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 83cd9fd..f573378 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -16,14 +16,13 @@ #include #include #include +#include #include "tester.h" #include "options.h" #include "options-exceptions.h" -//must be defined in a unit-specific source file such as "array-test.cpp" -void runTests(); - +void run_suites(const Options& options); static void show_error(const char* message); static void show_usage(const char* program_path); static void show_error_and_usage(const char* message, const char* program_path); @@ -56,7 +55,7 @@ int main(int argc, char* argv[]) try { - runTests(); + run_suites(options); } catch (const std::string& msg) { log_line(msg.data()); @@ -67,7 +66,28 @@ int main(int argc, char* argv[]) if (options.summary) summarize_tests(); - return get_tests_failed(); + return get_tests_failed_total(); +} + + +//run test suites +void runTests(); +void run_suites(const Options& options) +{ + using suite_runner_type = void(*)(); + std::map suites{ + //add one entry for each test suite to run + //suite-name and suite-runner, both in braces + {"array-test", runTests} + }; + + auto size = suites.size(); + for (auto suite : suites) { + start_suite(suite.first); + suite.second(); + if (size > 1 && options.summary) + summarize_suite(); + } } From 49b3bb65cb6f4d430f2709411e6b54942d96ccbc Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 09:09:15 -0400 Subject: [PATCH 07/42] Remove extra function declaration --- test/tester.h | 1 - 1 file changed, 1 deletion(-) diff --git a/test/tester.h b/test/tester.h index 010cedb..d4d79d6 100644 --- a/test/tester.h +++ b/test/tester.h @@ -28,7 +28,6 @@ void set_output(std::ostream& o); void log(const char* s); void log_line(const char* s); -unsigned get_tests_done(); unsigned get_tests_failed_total(); void start_suite(const std::string& name); From 4f1e2863730010c3e09b9f0882eff51b97c516a0 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 09:22:37 -0400 Subject: [PATCH 08/42] Run only suites indicated in options No cmd-line support yet to indicate suites to run --- test/driver.cpp | 16 +++++++++++----- test/options.h | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index f573378..34e6fab 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -81,12 +81,18 @@ void run_suites(const Options& options) {"array-test", runTests} }; + + // run all suites or only the suites indicated in options + bool run_all_suites = options.suites.empty(); auto size = suites.size(); - for (auto suite : suites) { - start_suite(suite.first); - suite.second(); - if (size > 1 && options.summary) - summarize_suite(); + for (const auto& suite : suites) { + auto& name = suite.first; + if (run_all_suites || options.suites.find(name) != std::string::npos) { + start_suite(name); + suite.second(); + if (size > 1 && options.summary) + summarize_suite(); + } } } diff --git a/test/options.h b/test/options.h index e3a10df..a6d9e24 100644 --- a/test/options.h +++ b/test/options.h @@ -30,6 +30,7 @@ struct Options { file_open_mode fom{ file_open_mode::no_file }; std::filesystem::path output_filepath; std::string command_name; + std::string suites; }; From 448ab41783f80d53aaf4bc8f6b706b6ade5fd85e Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 09:23:54 -0400 Subject: [PATCH 09/42] Fix comment --- test/driver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.cpp b/test/driver.cpp index 34e6fab..46ef24b 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -82,7 +82,7 @@ void run_suites(const Options& options) }; - // run all suites or only the suites indicated in options + // run all suites or only the suites indicates bool run_all_suites = options.suites.empty(); auto size = suites.size(); for (const auto& suite : suites) { From 1880f95036d8285c6b5e22f3be521b2f3c274b59 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 09:24:50 -0400 Subject: [PATCH 10/42] Add TODO comment --- test/driver.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/driver.cpp b/test/driver.cpp index 46ef24b..ab30590 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -82,7 +82,8 @@ void run_suites(const Options& options) }; - // run all suites or only the suites indicates + //run all suites or only the suites indicated in options + //TODO: change selection logic for suites indicated bool run_all_suites = options.suites.empty(); auto size = suites.size(); for (const auto& suite : suites) { From a8a12ba1e4efa983508cfe23cd20f0a49312c894 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 16:53:51 -0400 Subject: [PATCH 11/42] Format code --- test/tester.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/tester.h b/test/tester.h index d4d79d6..e34ca69 100644 --- a/test/tester.h +++ b/test/tester.h @@ -67,6 +67,7 @@ inline void is_zero(T value, const char* msg) verify(value == 0, msg); } + template inline void is_nonzero(T value, const char* msg) { @@ -74,6 +75,7 @@ inline void is_nonzero(T value, const char* msg) verify(value != 0, msg); } + template inline void is_negative(T value, const char* msg) { From bb61528ab73e659f3c5d730652338c40f3f35cdc Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 16:55:30 -0400 Subject: [PATCH 12/42] Move fn replace_all to separate source file --- test/options.cpp | 11 +---------- test/options.h | 2 -- test/test-stl-lite.vcxproj | 2 ++ test/test-stl-lite.vcxproj.filters | 6 ++++++ test/utils.cpp | 25 +++++++++++++++++++++++++ test/utils.h | 22 ++++++++++++++++++++++ 6 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 test/utils.cpp create mode 100644 test/utils.h diff --git a/test/options.cpp b/test/options.cpp index 93bdbd2..359244a 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -23,6 +23,7 @@ #include "options.h" #include "options-exceptions.h" +#include "utils.h" Options get_options(char* arguments[], const std::size_t size) { @@ -233,13 +234,3 @@ unsigned short get_fail_threshold(const std::string_view& sv) return value; } - - -//replace all instances of a substring with a new substring -void replace_all(std::string& str, const std::string& substr, const std::string& new_substr) -{ - auto pos = str.find(substr); - auto substr_size = substr.size(), new_substr_size = new_substr.size(); - for (; pos != std::string::npos; pos = str.find(substr, pos + new_substr_size)) - str.replace(pos, substr_size, new_substr); -} diff --git a/test/options.h b/test/options.h index a6d9e24..a3dc4dd 100644 --- a/test/options.h +++ b/test/options.h @@ -46,6 +46,4 @@ unsigned short get_fail_threshold(const std::string_view& value); bool strtobool(const std::string_view& value); -void replace_all(std::string& str, const std::string& substr, const std::string& new_substr); - #endif \ No newline at end of file diff --git a/test/test-stl-lite.vcxproj b/test/test-stl-lite.vcxproj index 4686419..663d87f 100644 --- a/test/test-stl-lite.vcxproj +++ b/test/test-stl-lite.vcxproj @@ -163,11 +163,13 @@ + + diff --git a/test/test-stl-lite.vcxproj.filters b/test/test-stl-lite.vcxproj.filters index d2fc38c..ccadc34 100644 --- a/test/test-stl-lite.vcxproj.filters +++ b/test/test-stl-lite.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -38,5 +41,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/test/utils.cpp b/test/utils.cpp new file mode 100644 index 0000000..f80f3c5 --- /dev/null +++ b/test/utils.cpp @@ -0,0 +1,25 @@ +/* +* utils.cpp +* Sean Murthy, Nick DeMarco +* (c) 2020 sigcpp https://sigcpp.github.io. See LICENSE.MD +* +* Attribution and copyright notice must be retained. +* - Attribution may be augmented to include additional authors +* - Copyright notice cannot be altered +* Attribution and copyright info may be relocated but they must be conspicuous. +* +* Define general-purpose utility functions and such +*/ + +#include + +#include "utils.h" + +//replace all instances of a substring with a new substring +void replace_all(std::string& str, const std::string& substr, const std::string& new_substr) +{ + auto pos = str.find(substr); + auto substr_size = substr.size(), new_substr_size = new_substr.size(); + for (; pos != std::string::npos; pos = str.find(substr, pos + new_substr_size)) + str.replace(pos, substr_size, new_substr); +} diff --git a/test/utils.h b/test/utils.h new file mode 100644 index 0000000..3a55187 --- /dev/null +++ b/test/utils.h @@ -0,0 +1,22 @@ +/* +* utils.h +* Sean Murthy +* (c) 2020 sigcpp https://sigcpp.github.io. See LICENSE.MD +* +* Attribution and copyright notice must be retained. +* - Attribution may be augmented to include additional authors +* - Copyright notice cannot be altered +* Attribution and copyright info may be relocated but they must be conspicuous. +* +* Declare generanl-purpose utility functions and such +*/ + +#ifndef STL_LITE_UTILS_H +#define STL_LITE_UTILS_H + +#include + + +void replace_all(std::string& str, const std::string& substr, const std::string& new_substr); + +#endif \ No newline at end of file From 7483f75ebf0ba978fcce9a37fa3a572f54bcea76 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 17:02:21 -0400 Subject: [PATCH 13/42] Change default header text to "Running $suite" --- test/options.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/options.h b/test/options.h index a3dc4dd..b935b0e 100644 --- a/test/options.h +++ b/test/options.h @@ -24,7 +24,7 @@ enum class file_open_mode { no_file, new_file, overwrite, append }; struct Options { bool header{ true }; bool summary{ true }; - std::string header_text{ "Running $cmd" }; + std::string header_text{ "Running $suite" }; pass_report_mode prm{ pass_report_mode::indicate }; unsigned short fail_threshold = 0; file_open_mode fom{ file_open_mode::no_file }; From 719b3fb18bfd9a37daee36a5f0dce211854408d0 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 17:02:55 -0400 Subject: [PATCH 14/42] Expand $suite macro in header text --- test/tester.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/tester.cpp b/test/tester.cpp index a19546a..9bdeae3 100644 --- a/test/tester.cpp +++ b/test/tester.cpp @@ -17,8 +17,10 @@ #include #include +#include "utils.h" #include "tester.h" + static std::string headerText("Running tests"); void set_header_text(std::string text) { @@ -91,9 +93,12 @@ void start_suite(const std::string& name) tests_done_suite = 0; tests_failed_suite = 0; + //print header text after expanding macro $suite if (!headerText.empty()) { - //TODO replace $suite with suite name before printing - *pOut << headerText << ":\n"; + const std::string suite_macro{ "$suite" }; + std::string suite_header{ headerText }; + replace_all(suite_header, "$suite", name); + *pOut << suite_header << ":\n"; } } From aadf25886e3c038c9e877a35c24241ad62cfb056 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 19:23:32 -0400 Subject: [PATCH 15/42] Add fn template split --- test/utils.cpp | 1 + test/utils.h | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/test/utils.cpp b/test/utils.cpp index f80f3c5..fe4cc12 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -12,6 +12,7 @@ */ #include +#include #include "utils.h" diff --git a/test/utils.h b/test/utils.h index 3a55187..6838fff 100644 --- a/test/utils.h +++ b/test/utils.h @@ -15,8 +15,29 @@ #define STL_LITE_UTILS_H #include - +#include void replace_all(std::string& str, const std::string& substr, const std::string& new_substr); +//split a delimited string or string_view into vector +template +std::vector split(const T& s, char delimiter) +{ + static_assert(std::is_same_v || std::is_same_v, + "requires std::string type or std::string_view type"); + + std::vector result; + typename T::size_type pos, last_pos{ 0 }; + while ((pos = s.find(delimiter, last_pos)) != T::npos) { + if (s[last_pos] != delimiter) + result.emplace_back(s.substr(last_pos, pos - last_pos)); + last_pos = pos + 1; + } + + if (last_pos < s.size()) + result.emplace_back(s.substr(last_pos, pos - last_pos)); + + return result; +} + #endif \ No newline at end of file From 859ec56493abac4c37106be38cc53840c28d3a4e Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 19:24:20 -0400 Subject: [PATCH 16/42] Rename field suites to suites_to_run --- test/options.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/options.h b/test/options.h index b935b0e..aeba546 100644 --- a/test/options.h +++ b/test/options.h @@ -30,7 +30,7 @@ struct Options { file_open_mode fom{ file_open_mode::no_file }; std::filesystem::path output_filepath; std::string command_name; - std::string suites; + std::string suites_to_run; }; From c9bbd9439bbbf9af279cc0aa282e6826747dd452 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 19:25:05 -0400 Subject: [PATCH 17/42] Support -run option --- test/options.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/options.cpp b/test/options.cpp index 359244a..cdebb4d 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -52,7 +52,7 @@ Options get_options(char* arguments[], const std::size_t size) //names in name-value pair for cmd-line options constexpr std::string_view option_name_header{ "-h" }, option_name_header_text{ "-ht" }, option_name_summary{ "-s" }, option_name_prm{ "-p" }, option_name_threshold{ "-t" }, - option_name_file_start{ "-f" }; + option_name_file_start{ "-f" }, option_name_run{ "-run" }; std::string_view prm_value; std::string output_filepath_value; @@ -77,12 +77,14 @@ Options get_options(char* arguments[], const std::size_t size) options.header = strtobool(value); else if (name == option_name_header_text) options.header_text = value; - else if (name == option_name_prm) - prm_value = value; //delay converting prm to enum until after file open mode is known else if (name == option_name_summary) options.summary = strtobool(value); + else if (name == option_name_prm) + prm_value = value; //delay converting prm to enum until after file open mode is known else if (name == option_name_threshold) options.fail_threshold = get_fail_threshold(value); + else if (name == option_name_run) + options.suites_to_run = value; else if (name._Starts_with(option_name_file_start)) { options.fom = get_file_open_mode(name); output_filepath_value = value; From a1f89823204126b5f9d3dd7e83c4bc4a519bd119 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 19:42:01 -0400 Subject: [PATCH 18/42] Implement -run option --- test/driver.cpp | 61 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index ab30590..7548cbd 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -17,11 +17,15 @@ #include #include #include +#include +#include +#include "utils.h" #include "tester.h" #include "options.h" #include "options-exceptions.h" + void run_suites(const Options& options); static void show_error(const char* message); static void show_usage(const char* program_path); @@ -57,10 +61,17 @@ int main(int argc, char* argv[]) try { run_suites(options); } - catch (const std::string& msg) { - log_line(msg.data()); - if (options.fom == file_open_mode::no_file) - show_error(msg.data()); + catch (const cmd_line_error& cle) { + show_error_and_usage(cle.what(), argv[0]); + return -5; + } + catch (const std::exception& e) { + show_error((std::string{ "Unexpected error: " } +e.what()).data()); + return -6; + } + catch (...) { + show_error("Unexpected error"); + return -7; } if (options.summary) @@ -81,15 +92,30 @@ void run_suites(const Options& options) {"array-test", runTests} }; + //build a collection of suites to run if necessary + //options.suites_to_run is empty or a semi-colon delimited list of suite names + const auto suites_to_run = split(options.suites_to_run, ';'); + auto run_all_suites = suites_to_run.empty(); + + //check that the suites specified are actually defined + //this check is not required to run the suites, but is included to inform the user of the issue + //silently ignoring leaves the user unaware of the reason a specified suite doesn't run + if (!run_all_suites) { + auto end_suites = suites.cend(); + for (auto& suite_name : suites_to_run) { + if (suites.find(suite_name) == end_suites) { + assert(false); + throw invalid_option_value{ std::string{"suite "} + suite_name + " not defined" }; + } + } + } //run all suites or only the suites indicated in options - //TODO: change selection logic for suites indicated - bool run_all_suites = options.suites.empty(); auto size = suites.size(); + auto begin = suites_to_run.cbegin(), end = suites_to_run.cend(); for (const auto& suite : suites) { - auto& name = suite.first; - if (run_all_suites || options.suites.find(name) != std::string::npos) { - start_suite(name); + if (run_all_suites || std::find(begin, end, suite.first) != end) { + start_suite(suite.first); suite.second(); if (size > 1 && options.summary) summarize_suite(); @@ -131,14 +157,15 @@ static void show_usage(const char* program_path) "Angle brackets are placeholders for option values:\n\n"; std::cout << - " -h \n" - " -ht
\n" - " -s \n" - " -p \n" - " -t \n" - " -fn \n" - " -fo \n" - " -fa \n" + " -h \n" + " -ht
\n" + " -s \n" + " -p \n" + " -t \n" + " -run \n" + " -fn \n" + " -fo \n" + " -fa \n" "\n"; std::cout << From aad13fc292289c21e7cb787fa8448ab9ffc03721 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 19:46:24 -0400 Subject: [PATCH 19/42] Rename runTests to array_test --- test/array-test.cpp | 2 +- test/driver.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/array-test.cpp b/test/array-test.cpp index 1b7f29b..5cd3e82 100644 --- a/test/array-test.cpp +++ b/test/array-test.cpp @@ -18,7 +18,7 @@ #include "tester.h" -void runTests() +void array_test() { using sigcpp::array; diff --git a/test/driver.cpp b/test/driver.cpp index 7548cbd..44034d6 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -82,14 +82,14 @@ int main(int argc, char* argv[]) //run test suites -void runTests(); +void array_test(); void run_suites(const Options& options) { using suite_runner_type = void(*)(); std::map suites{ //add one entry for each test suite to run //suite-name and suite-runner, both in braces - {"array-test", runTests} + {"array-test", array_test} }; //build a collection of suites to run if necessary From 79bc61f1cde581f22ba5ccd960419ffc48618b0e Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 19:50:26 -0400 Subject: [PATCH 20/42] Add comment on declaring and specifying suite runners --- test/driver.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/driver.cpp b/test/driver.cpp index 44034d6..273d7fb 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -81,8 +81,11 @@ int main(int argc, char* argv[]) } -//run test suites +//declare suite runners: one for each test suite +//also add an entry in the "suites" map for each suite defined void array_test(); + + void run_suites(const Options& options) { using suite_runner_type = void(*)(); From 2d497340797b5277a1f005fdb4267b2c1e1f2287 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Fri, 12 Jun 2020 20:08:26 -0400 Subject: [PATCH 21/42] Format code --- test/driver.cpp | 2 +- test/utils.h | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 273d7fb..62ebe93 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -108,7 +108,7 @@ void run_suites(const Options& options) for (auto& suite_name : suites_to_run) { if (suites.find(suite_name) == end_suites) { assert(false); - throw invalid_option_value{ std::string{"suite "} + suite_name + " not defined" }; + throw invalid_option_value{ std::string{"suite "} +suite_name + " not defined" }; } } } diff --git a/test/utils.h b/test/utils.h index 6838fff..15952e2 100644 --- a/test/utils.h +++ b/test/utils.h @@ -23,21 +23,21 @@ void replace_all(std::string& str, const std::string& substr, const std::string& template std::vector split(const T& s, char delimiter) { - static_assert(std::is_same_v || std::is_same_v, - "requires std::string type or std::string_view type"); + static_assert(std::is_same_v || std::is_same_v, + "requires std::string type or std::string_view type"); - std::vector result; - typename T::size_type pos, last_pos{ 0 }; - while ((pos = s.find(delimiter, last_pos)) != T::npos) { - if (s[last_pos] != delimiter) - result.emplace_back(s.substr(last_pos, pos - last_pos)); - last_pos = pos + 1; - } + std::vector result; + typename T::size_type pos, last_pos{ 0 }; + while ((pos = s.find(delimiter, last_pos)) != T::npos) { + if (s[last_pos] != delimiter) + result.emplace_back(s.substr(last_pos, pos - last_pos)); + last_pos = pos + 1; + } - if (last_pos < s.size()) - result.emplace_back(s.substr(last_pos, pos - last_pos)); + if (last_pos < s.size()) + result.emplace_back(s.substr(last_pos, pos - last_pos)); - return result; + return result; } #endif \ No newline at end of file From a3563a033d98fa268afc22c57ccf6b544f309aaf Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 07:47:52 -0400 Subject: [PATCH 22/42] Return early on empty input; use push_back --- test/utils.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/utils.h b/test/utils.h index 15952e2..9acd936 100644 --- a/test/utils.h +++ b/test/utils.h @@ -27,15 +27,18 @@ std::vector split(const T& s, char delimiter) "requires std::string type or std::string_view type"); std::vector result; + if (s.empty()) + return result; + typename T::size_type pos, last_pos{ 0 }; while ((pos = s.find(delimiter, last_pos)) != T::npos) { if (s[last_pos] != delimiter) - result.emplace_back(s.substr(last_pos, pos - last_pos)); + result.push_back(s.substr(last_pos, pos - last_pos)); last_pos = pos + 1; } if (last_pos < s.size()) - result.emplace_back(s.substr(last_pos, pos - last_pos)); + result.push_back(s.substr(last_pos, pos - last_pos)); return result; } From 9e534f2bd69f95ffa1db50baf531873d46ce88f6 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 08:00:54 -0400 Subject: [PATCH 23/42] Use unordered map --- test/driver.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 62ebe93..eb55915 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include @@ -89,10 +89,11 @@ void array_test(); void run_suites(const Options& options) { using suite_runner_type = void(*)(); - std::map suites{ + std::unordered_map suites{ //add one entry for each test suite to run //suite-name and suite-runner, both in braces - {"array-test", array_test} + //not necessary, but recommend using the suite_runner function's name as the suite name + {"array_test", array_test} }; //build a collection of suites to run if necessary From 3beef75a38fc5b153b81bc4978da6b6624e47a30 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 08:01:51 -0400 Subject: [PATCH 24/42] Update comment --- test/driver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.cpp b/test/driver.cpp index eb55915..7111c37 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -103,7 +103,7 @@ void run_suites(const Options& options) //check that the suites specified are actually defined //this check is not required to run the suites, but is included to inform the user of the issue - //silently ignoring leaves the user unaware of the reason a specified suite doesn't run + //silently ignoring an unfound suite leaves the user unaware of the reason the suite doesn't run if (!run_all_suites) { auto end_suites = suites.cend(); for (auto& suite_name : suites_to_run) { From 662a60bbbbe01517e392e756229872c734e20688 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 09:29:48 -0400 Subject: [PATCH 25/42] Use UIS; prefer assignment when auto type --- test/driver.cpp | 2 +- test/options.cpp | 4 ++-- test/utils.cpp | 6 +++--- test/utils.h | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 7111c37..2bdcbae 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -147,7 +147,7 @@ static void show_error(const char* message) static void show_usage(const char* program_path) { - std::string program_filename = std::filesystem::path{ program_path }.filename().string(); + auto program_filename = std::filesystem::path{ program_path }.filename().string(); std::cout << "Usage: " << program_filename << " {option_name option_value}\n\n"; diff --git a/test/options.cpp b/test/options.cpp index cdebb4d..0801504 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -222,8 +222,8 @@ unsigned short get_fail_threshold(const std::string_view& sv) auto begin = sv.data(), end = begin + sv.size(); auto result = std::from_chars(begin, end, value); - //check for "out of range" - bool success = result.ec == std::errc(); + //check for conversion errors + auto success = result.ec == std::errc(); assert(success); if (!success) throw invalid_option_value{ sv }; diff --git a/test/utils.cpp b/test/utils.cpp index fe4cc12..62f00b6 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -1,6 +1,6 @@ /* * utils.cpp -* Sean Murthy, Nick DeMarco +* Sean Murthy * (c) 2020 sigcpp https://sigcpp.github.io. See LICENSE.MD * * Attribution and copyright notice must be retained. @@ -19,8 +19,8 @@ //replace all instances of a substring with a new substring void replace_all(std::string& str, const std::string& substr, const std::string& new_substr) { - auto pos = str.find(substr); - auto substr_size = substr.size(), new_substr_size = new_substr.size(); + auto pos{ str.find(substr) }; + auto substr_size{ substr.size() }, new_substr_size{ new_substr.size() }; for (; pos != std::string::npos; pos = str.find(substr, pos + new_substr_size)) str.replace(pos, substr_size, new_substr); } diff --git a/test/utils.h b/test/utils.h index 9acd936..c94e69c 100644 --- a/test/utils.h +++ b/test/utils.h @@ -15,6 +15,7 @@ #define STL_LITE_UTILS_H #include +#include #include void replace_all(std::string& str, const std::string& substr, const std::string& new_substr); From 5a39e23664c2cf3e9ac1cb489dadcff1d3ff6014 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 09:37:11 -0400 Subject: [PATCH 26/42] Move verification helpers to verifiers.h Suite runners should include verifiers.h instead of including tester.h --- test/array-test.cpp | 2 +- test/test-stl-lite.vcxproj | 1 + test/test-stl-lite.vcxproj.filters | 3 ++ test/tester.h | 64 +---------------------- test/verifiers.h | 84 ++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 64 deletions(-) create mode 100644 test/verifiers.h diff --git a/test/array-test.cpp b/test/array-test.cpp index 5cd3e82..91dd933 100644 --- a/test/array-test.cpp +++ b/test/array-test.cpp @@ -16,7 +16,7 @@ #include "../include/array.h" -#include "tester.h" +#include "verifiers.h" void array_test() { diff --git a/test/test-stl-lite.vcxproj b/test/test-stl-lite.vcxproj index 663d87f..e970e44 100644 --- a/test/test-stl-lite.vcxproj +++ b/test/test-stl-lite.vcxproj @@ -170,6 +170,7 @@ + diff --git a/test/test-stl-lite.vcxproj.filters b/test/test-stl-lite.vcxproj.filters index ccadc34..d909634 100644 --- a/test/test-stl-lite.vcxproj.filters +++ b/test/test-stl-lite.vcxproj.filters @@ -44,5 +44,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/test/tester.h b/test/tester.h index e34ca69..d90f6b6 100644 --- a/test/tester.h +++ b/test/tester.h @@ -37,66 +37,4 @@ void summarize_tests(); void verify(bool success, const char* msg); -// the following functions and templates are helper functions short-hand to verify -// call the helpers instead of calling verify directly - -inline void is_true(bool value, const char* msg) -{ - verify(value, msg); -} - - -inline void is_false(bool value, const char* msg) -{ - verify(!value, msg); -} - - -//TODO replace this template with a macro to reduce template instantiations -template -constexpr bool is_nonbool_arithmetic() -{ - return std::is_integral_v && !std::is_same_v; -} - - -template -inline void is_zero(T value, const char* msg) -{ - static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); - verify(value == 0, msg); -} - - -template -inline void is_nonzero(T value, const char* msg) -{ - static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); - verify(value != 0, msg); -} - - -template -inline void is_negative(T value, const char* msg) -{ - static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); - verify(value < 0, msg); -} - - -template -inline void is_nonnegative(T value, const char* msg) -{ - static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); - verify(value >= 0, msg); -} - - -template -inline void is_positive(T value, const char* msg) -{ - static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); - verify(value > 0, msg); -} - -#endif \ No newline at end of file +#endif diff --git a/test/verifiers.h b/test/verifiers.h new file mode 100644 index 0000000..3222837 --- /dev/null +++ b/test/verifiers.h @@ -0,0 +1,84 @@ +/* +* verifiers.h +* Sean Murthy +* (c) 2020 sigcpp https://sigcpp.github.io. See LICENSE.MD +* +* Attribution and copyright notice must be retained. +* - Attribution may be augmented to include additional authors +* - Copyright notice cannot be altered +* Attribution and copyright info may be relocated but they must be conspicuous. +* +* Declare and define verifiers: verifiers are short-hand functions and call verify internally +* suite runners should call verifiers instead of calling verify function directly +*/ + +#ifndef STL_LITE_VERIFIERS_H +#define STL_LITE_VERIFIERS_H + +#include + +//intentionally declared here instead of including "tester.h" +//suite runners need access only to verifiers and nothing else in the tester +void verify(bool success, const char* msg); + + +inline void is_true(bool value, const char* msg) +{ + verify(value, msg); +} + + +inline void is_false(bool value, const char* msg) +{ + verify(!value, msg); +} + + +//TODO replace this template with a macro to reduce template instantiations +template +constexpr bool is_nonbool_arithmetic() +{ + return std::is_integral_v && !std::is_same_v; +} + + +template +inline void is_zero(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value == 0, msg); +} + + +template +inline void is_nonzero(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value != 0, msg); +} + + +template +inline void is_negative(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value < 0, msg); +} + + +template +inline void is_nonnegative(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value >= 0, msg); +} + + +template +inline void is_positive(T value, const char* msg) +{ + static_assert(is_nonbool_arithmetic(), "requires non-bool arithmetic type"); + verify(value > 0, msg); +} + +#endif \ No newline at end of file From 0dd3329a5e5ae24d27cd02d915ece80570ff15ae Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 11:41:38 -0400 Subject: [PATCH 27/42] Move test suites collection to suites.cpp --- test/driver.cpp | 32 ++++++++------------ test/suites.cpp | 47 ++++++++++++++++++++++++++++++ test/suites.h | 23 +++++++++++++++ test/test-stl-lite.vcxproj | 2 ++ test/test-stl-lite.vcxproj.filters | 6 ++++ test/utils.h | 2 +- 6 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 test/suites.cpp create mode 100644 test/suites.h diff --git a/test/driver.cpp b/test/driver.cpp index 2bdcbae..7491011 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -21,6 +21,7 @@ #include #include "utils.h" +#include "suites.h" #include "tester.h" #include "options.h" #include "options-exceptions.h" @@ -81,32 +82,23 @@ int main(int argc, char* argv[]) } -//declare suite runners: one for each test suite -//also add an entry in the "suites" map for each suite defined -void array_test(); - - +//run test suites in sequence: runs all suites defined or only those specified on cmd-line void run_suites(const Options& options) { - using suite_runner_type = void(*)(); - std::unordered_map suites{ - //add one entry for each test suite to run - //suite-name and suite-runner, both in braces - //not necessary, but recommend using the suite_runner function's name as the suite name - {"array_test", array_test} - }; - - //build a collection of suites to run if necessary + //retrieve all test suites defined + auto suites = get_test_suites(); + + //build a collection of suite names to run if necessary //options.suites_to_run is empty or a semi-colon delimited list of suite names - const auto suites_to_run = split(options.suites_to_run, ';'); - auto run_all_suites = suites_to_run.empty(); + const auto suite_names_to_run = split(options.suites_to_run, ';'); + auto run_all_suites = suite_names_to_run.empty(); - //check that the suites specified are actually defined - //this check is not required to run the suites, but is included to inform the user of the issue + //check that the suite names specified correspond to suites defined + //this check is not required to run the suites, but is included to inform the user of any issues //silently ignoring an unfound suite leaves the user unaware of the reason the suite doesn't run if (!run_all_suites) { auto end_suites = suites.cend(); - for (auto& suite_name : suites_to_run) { + for (auto& suite_name : suite_names_to_run) { if (suites.find(suite_name) == end_suites) { assert(false); throw invalid_option_value{ std::string{"suite "} +suite_name + " not defined" }; @@ -116,7 +108,7 @@ void run_suites(const Options& options) //run all suites or only the suites indicated in options auto size = suites.size(); - auto begin = suites_to_run.cbegin(), end = suites_to_run.cend(); + auto begin = suite_names_to_run.cbegin(), end = suite_names_to_run.cend(); for (const auto& suite : suites) { if (run_all_suites || std::find(begin, end, suite.first) != end) { start_suite(suite.first); diff --git a/test/suites.cpp b/test/suites.cpp new file mode 100644 index 0000000..6b65c24 --- /dev/null +++ b/test/suites.cpp @@ -0,0 +1,47 @@ +/* +* suites.cpp +* Sean Murthy +* (c) 2020 sigcpp https://sigcpp.github.io. See LICENSE.MD +* +* Attribution and copyright notice must be retained. +* - Attribution may be augmented to include additional authors +* - Copyright notice cannot be altered +* Attribution and copyright info may be relocated but they must be conspicuous. +* +* Define functions to manage collection of test suites +*/ + +#include +#include + +#include "suites.h" + +//all test suites defined +static std::unordered_map test_suites; + +//flag to denote if suites collection is already built +static bool collection_built{ true }; + + +void build_suites_collection(); + +//provide read-only access to collection of all test suites defined +//build collection "just in time" if it has not already been built +const std::unordered_map& get_test_suites() +{ + if (collection_built) + build_suites_collection(); + + collection_built = false; + return test_suites; +} + + +//this function is intentionally placed at the end of file to make it easier and safer to add test suites +//the collection is as good as being built with a list initializer ctor, but the technique used here makes it +//easier to add suites +void build_suites_collection() +{ + void array_test(); + test_suites["array_test"] = array_test; +} diff --git a/test/suites.h b/test/suites.h new file mode 100644 index 0000000..8fffdb4 --- /dev/null +++ b/test/suites.h @@ -0,0 +1,23 @@ +/* +* suites.h +* Sean Murthy +* (c) 2020 sigcpp https://sigcpp.github.io. See LICENSE.MD +* +* Attribution and copyright notice must be retained. +* - Attribution may be augmented to include additional authors +* - Copyright notice cannot be altered +* Attribution and copyright info may be relocated but they must be conspicuous. +* +* Declare types and functions to access collection of test suites +*/ + +#ifndef STL_LITE_SUITES_H +#define STL_LITE_SUITES_H + +#include +#include + +using suite_runner_type = void(*)(); +const std::unordered_map& get_test_suites(); + +#endif diff --git a/test/test-stl-lite.vcxproj b/test/test-stl-lite.vcxproj index e970e44..2243c1e 100644 --- a/test/test-stl-lite.vcxproj +++ b/test/test-stl-lite.vcxproj @@ -162,12 +162,14 @@ + + diff --git a/test/test-stl-lite.vcxproj.filters b/test/test-stl-lite.vcxproj.filters index d909634..1d63bbe 100644 --- a/test/test-stl-lite.vcxproj.filters +++ b/test/test-stl-lite.vcxproj.filters @@ -30,6 +30,9 @@ Source Files + + Source Files + @@ -47,5 +50,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/test/utils.h b/test/utils.h index c94e69c..dd95172 100644 --- a/test/utils.h +++ b/test/utils.h @@ -8,7 +8,7 @@ * - Copyright notice cannot be altered * Attribution and copyright info may be relocated but they must be conspicuous. * -* Declare generanl-purpose utility functions and such +* Declare general-purpose utility functions and such */ #ifndef STL_LITE_UTILS_H From c332c9ec33683607d035f4e2901371a4c1cc1f9d Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 11:45:21 -0400 Subject: [PATCH 28/42] Update test/array-test.cpp `array-test.cpp` is not really part of this PR. Only the `is_true` verifier is used everywhere for startters. Other verifiers are yet to to be used anywhere. I am accepting the suggestions made right now, but I recommend not wasting effort. Co-authored-by: Ray Chen --- test/array-test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/array-test.cpp b/test/array-test.cpp index 91dd933..2737bd3 100644 --- a/test/array-test.cpp +++ b/test/array-test.cpp @@ -30,7 +30,7 @@ void array_test() //capacity - is_true(!s.empty(), "s.empty()"); + is_false(s.empty(), "s.empty()"); is_true(s.size() == 3, "s.size()"); is_true(s.max_size() == s.size(), "s.max_size()"); @@ -121,4 +121,4 @@ void array_test() for (std::size_t idx = 0; idx < m.size() && swapTest; ++idx) swapTest = m[idx] == mExpected[idx] && n[idx] == nExpected[idx]; is_true(swapTest, "m.swap(n)"); -} \ No newline at end of file +} From 01d08e6197c2813bf546cb7a2e06a5055789c280 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 11:45:34 -0400 Subject: [PATCH 29/42] Update test/array-test.cpp Co-authored-by: Ray Chen --- test/array-test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/array-test.cpp b/test/array-test.cpp index 2737bd3..755684b 100644 --- a/test/array-test.cpp +++ b/test/array-test.cpp @@ -34,7 +34,7 @@ void array_test() is_true(s.size() == 3, "s.size()"); is_true(s.max_size() == s.size(), "s.max_size()"); - is_true(!p.empty(), "p.empty()"); + is_false(p.empty(), "p.empty()"); is_true(p.size() == 5, "p.size()"); is_true(p.max_size() == p.size(), "p.max_size()"); From 07054376e616ee5214fdfcae6eee7a6b2c736e58 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 11:49:50 -0400 Subject: [PATCH 30/42] Update test/options.h Co-authored-by: Ray Chen --- test/options.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/options.h b/test/options.h index aeba546..626eb7e 100644 --- a/test/options.h +++ b/test/options.h @@ -26,7 +26,7 @@ struct Options { bool summary{ true }; std::string header_text{ "Running $suite" }; pass_report_mode prm{ pass_report_mode::indicate }; - unsigned short fail_threshold = 0; + unsigned short fail_threshold{ 0 }; file_open_mode fom{ file_open_mode::no_file }; std::filesystem::path output_filepath; std::string command_name; @@ -46,4 +46,4 @@ unsigned short get_fail_threshold(const std::string_view& value); bool strtobool(const std::string_view& value); -#endif \ No newline at end of file +#endif From 690bf42da8a6b8d75acd55d96b26aff6f53659a1 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 12:47:51 -0400 Subject: [PATCH 31/42] "Simplify" test suite definition Define a set of macros to create a 1-step means of adding a test suite to suites collection. Made the definition process look declarative. --- test/suites.cpp | 63 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/test/suites.cpp b/test/suites.cpp index 6b65c24..162c047 100644 --- a/test/suites.cpp +++ b/test/suites.cpp @@ -8,7 +8,9 @@ * - Copyright notice cannot be altered * Attribution and copyright info may be relocated but they must be conspicuous. * -* Define functions to manage collection of test suites +* Define a collection of test suites and functions to manage the collection +* Edit this with a lot of care: many carefully designed macros to make suite definition easy and safe +* Go to the section at the end of this file to add a test suite */ #include @@ -16,32 +18,61 @@ #include "suites.h" -//all test suites defined +//keyed collection of all test suites defined static std::unordered_map test_suites; -//flag to denote if suites collection is already built -static bool collection_built{ true }; +//macros to facilitate 1-step definition and addition of suite runners to suites collection +//TEST_SUITE is the only macro necessary, but the other macros are defined and used so that +//the section where test suites are added looks cohesive + +#define DECLARE_BUILD_SUITES_COLLECTION void build_suites_collection(); + +#define BUILD_SUITES_COLLECTION \ + if (collection_built) \ + build_suites_collection(); + +#define START_SUITES_COLLECTION \ +void build_suites_collection() \ +{ + +#define END_SUITES_COLLECTION \ +} + +//macro to declare a suite runner function and to add it to the suites collection +//Example: TEST_SUITE(array_test) is to add the following two lines of code: +// void array_test(); +// test_suites["array_test"] = array_test; +#define TEST_SUITE(SUITE_NAME) \ + void SUITE_NAME(); \ + test_suites[#SUITE_NAME] = SUITE_NAME; -void build_suites_collection(); +//flag to denote if suites collection is already built +static bool collection_built{ true }; //provide read-only access to collection of all test suites defined //build collection "just in time" if it has not already been built const std::unordered_map& get_test_suites() { - if (collection_built) - build_suites_collection(); - + DECLARE_BUILD_SUITES_COLLECTION; // void build_suites_collection(); + BUILD_SUITES_COLLECTION; // if (collection_built) build_suites_collection(); + collection_built = false; return test_suites; } -//this function is intentionally placed at the end of file to make it easier and safer to add test suites -//the collection is as good as being built with a list initializer ctor, but the technique used here makes it -//easier to add suites -void build_suites_collection() -{ - void array_test(); - test_suites["array_test"] = array_test; -} +//this section is intentionally placed at the end of file to make it easier and safer to add test suites +//the collection is as good as being built with a initializer-list ctor, but the technique used here provides +//a 1-step approach instead of the 2-step approach that would be necessary if initializer-list ctor is used +//(2 steps: declare suite runner and add suite runner to the collection) + +START_SUITES_COLLECTION //same as void build_suites_collection() { + + //add one line per test suite: macro parameter should be the name of a suite runner function + //the semi-colon at the end of macro invocation is not required but its use make things look authentic + + TEST_SUITE(array_test); + +// do not add/edit anything after this line +END_SUITES_COLLECTION // same as } From f76a21b4f1fb127dd07c434fe6239936b46f95d0 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 13:08:51 -0400 Subject: [PATCH 32/42] Remove assertion on empty suite name --- test/tester.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/tester.cpp b/test/tester.cpp index 9bdeae3..bf8b291 100644 --- a/test/tester.cpp +++ b/test/tester.cpp @@ -15,13 +15,11 @@ #include #include #include -#include #include "utils.h" #include "tester.h" - -static std::string headerText("Running tests"); +static std::string headerText("Running $suite:"); void set_header_text(std::string text) { headerText = text; @@ -82,7 +80,6 @@ static std::string suite_name; static unsigned suites_run; void start_suite(const std::string& name) { - assert(!name.empty()); suite_name = name; ++suites_run; @@ -153,7 +150,6 @@ void verify(bool success, const char* hint) std::ostringstream message; - if (success) { if (prm == pass_report_mode::indicate) message << '.'; From a8540fc0dad6a4c4ae64f84eee7ad07d267caf8a Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 17:10:54 -0400 Subject: [PATCH 33/42] Rename variable; improve comment --- test/suites.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/suites.cpp b/test/suites.cpp index 162c047..05ebd90 100644 --- a/test/suites.cpp +++ b/test/suites.cpp @@ -9,7 +9,7 @@ * Attribution and copyright info may be relocated but they must be conspicuous. * * Define a collection of test suites and functions to manage the collection -* Edit this with a lot of care: many carefully designed macros to make suite definition easy and safe +* Edit this file with a lot of care: many carefully designed macros to make suite definition easy and safe * Go to the section at the end of this file to add a test suite */ @@ -23,12 +23,12 @@ static std::unordered_map test_suites; //macros to facilitate 1-step definition and addition of suite runners to suites collection //TEST_SUITE is the only macro necessary, but the other macros are defined and used so that -//the section where test suites are added looks cohesive +//the section where test suites are added looks declarative and cohesive #define DECLARE_BUILD_SUITES_COLLECTION void build_suites_collection(); #define BUILD_SUITES_COLLECTION \ - if (collection_built) \ + if (collection_not_built) \ build_suites_collection(); #define START_SUITES_COLLECTION \ @@ -48,16 +48,16 @@ void build_suites_collection() \ test_suites[#SUITE_NAME] = SUITE_NAME; //flag to denote if suites collection is already built -static bool collection_built{ true }; +static bool collection_not_built{ true }; //provide read-only access to collection of all test suites defined //build collection "just in time" if it has not already been built const std::unordered_map& get_test_suites() { DECLARE_BUILD_SUITES_COLLECTION; // void build_suites_collection(); - BUILD_SUITES_COLLECTION; // if (collection_built) build_suites_collection(); + BUILD_SUITES_COLLECTION; // if (collection_not_built) build_suites_collection(); - collection_built = false; + collection_not_built = false; return test_suites; } From 561749ac8174a04f8507b7aaf034aedd03da67fb Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 17:14:50 -0400 Subject: [PATCH 34/42] Fix comments, format code --- test/driver.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 7491011..bab305d 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -82,36 +82,37 @@ int main(int argc, char* argv[]) } -//run test suites in sequence: runs all suites defined or only those specified on cmd-line +//run test suites in sequence: runs all suites defined or only those whose names are specified in options void run_suites(const Options& options) { //retrieve all test suites defined auto suites = get_test_suites(); - //build a collection of suite names to run if necessary + //build a collection of suite names specified in the options structucre //options.suites_to_run is empty or a semi-colon delimited list of suite names - const auto suite_names_to_run = split(options.suites_to_run, ';'); - auto run_all_suites = suite_names_to_run.empty(); + const auto names_to_run = split(options.suites_to_run, ';'); + auto run_all_suites = names_to_run.empty(); - //check that the suite names specified correspond to suites defined - //this check is not required to run the suites, but is included to inform the user of any issues + //check that the suite names specified in options correspond to suites defined + //this check is not required to run the suites, but is included to inform the user of any issues; //silently ignoring an unfound suite leaves the user unaware of the reason the suite doesn't run if (!run_all_suites) { auto end_suites = suites.cend(); - for (auto& suite_name : suite_names_to_run) { + for (auto& suite_name : names_to_run) { if (suites.find(suite_name) == end_suites) { assert(false); - throw invalid_option_value{ std::string{"suite "} +suite_name + " not defined" }; + throw invalid_option_value{ std::string{"test suite "} +suite_name + " not defined" }; } } } //run all suites or only the suites indicated in options auto size = suites.size(); - auto begin = suite_names_to_run.cbegin(), end = suite_names_to_run.cend(); + auto begin_names_to_run = names_to_run.cbegin(), end_names_to_run = names_to_run.cend(); for (const auto& suite : suites) { - if (run_all_suites || std::find(begin, end, suite.first) != end) { - start_suite(suite.first); + const auto& key = suite.first; + if (run_all_suites || std::find(begin_names_to_run, end_names_to_run, key) != end_names_to_run) { + start_suite(key); suite.second(); if (size > 1 && options.summary) summarize_suite(); From 062a09246ec923edf564759ae7d5467f6782364d Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 17:50:13 -0400 Subject: [PATCH 35/42] define suites_map_type; add custom error class --- test/suites.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/suites.h b/test/suites.h index 8fffdb4..9f09db0 100644 --- a/test/suites.h +++ b/test/suites.h @@ -16,8 +16,22 @@ #include #include +#include using suite_runner_type = void(*)(); -const std::unordered_map& get_test_suites(); +using suites_map_type = std::unordered_map; + +const suites_map_type& get_test_suites(); + + +//error when adding a test suite to collection of suites +class test_suite_add_error : public std::runtime_error { + +public: + test_suite_add_error(const std::string& base, const std::string& name) + : std::runtime_error{ base + ": " + name } {} + +}; + #endif From da64ef4cc29b14217f00d5dde5c7a54fad45bf3d Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 17:51:49 -0400 Subject: [PATCH 36/42] Check duplicate test-suite name and other errors --- test/suites.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/suites.cpp b/test/suites.cpp index 05ebd90..79fd514 100644 --- a/test/suites.cpp +++ b/test/suites.cpp @@ -19,7 +19,18 @@ #include "suites.h" //keyed collection of all test suites defined -static std::unordered_map test_suites; +static suites_map_type test_suites; + +//add an entry to the suites collection, throwing an appropriate exception if insertion fails +static void checked_insert(suites_map_type& suites, const std::string& name, suite_runner_type runner) +{ + if (!suites.insert({ name, runner }).second) { + if (suites.find(name) == suites.end()) + throw test_suite_add_error("unknown error adding test suite", name); + else + throw test_suite_add_error("duplicate test-suite name", name); + } +} //macros to facilitate 1-step definition and addition of suite runners to suites collection //TEST_SUITE is the only macro necessary, but the other macros are defined and used so that @@ -40,12 +51,12 @@ void build_suites_collection() \ //macro to declare a suite runner function and to add it to the suites collection -//Example: TEST_SUITE(array_test) is to add the following two lines of code: +//Example: TEST_SUITE(array_test) adds the following two lines of code: // void array_test(); -// test_suites["array_test"] = array_test; +// checked_insert(test_suites, "array_test", array_test; #define TEST_SUITE(SUITE_NAME) \ void SUITE_NAME(); \ - test_suites[#SUITE_NAME] = SUITE_NAME; + checked_insert(test_suites, #SUITE_NAME, SUITE_NAME); //flag to denote if suites collection is already built static bool collection_not_built{ true }; From 88b9fbab31863e183c74bcef2cab4b173c1ac2cf Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 17:52:17 -0400 Subject: [PATCH 37/42] Handle test-suite addition error --- test/driver.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index bab305d..38ed67d 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -66,13 +66,17 @@ int main(int argc, char* argv[]) show_error_and_usage(cle.what(), argv[0]); return -5; } + catch (const test_suite_add_error& tae) { + show_error(tae.what()); + return -6; + } catch (const std::exception& e) { show_error((std::string{ "Unexpected error: " } +e.what()).data()); - return -6; + return -7; } catch (...) { show_error("Unexpected error"); - return -7; + return -8; } if (options.summary) From b613060fdb626cd80082908638ba2052e272020f Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 20:27:05 -0400 Subject: [PATCH 38/42] Rename fn. message and move it to utils --- test/options-exceptions.h | 17 +++++------------ test/utils.cpp | 11 +++++++++++ test/utils.h | 2 ++ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/test/options-exceptions.h b/test/options-exceptions.h index e695103..3db5600 100644 --- a/test/options-exceptions.h +++ b/test/options-exceptions.h @@ -19,23 +19,16 @@ #include #include -static std::string message(const std::string_view& base, const std::string& extra = "") -{ - std::string msg{ base }; - if (!extra.empty()) - msg += ": " + extra; - - return msg; -} +#include "utils.h" //base class for cmd-line errors: no public ctors to force use of a specialized error class cmd_line_error : public std::runtime_error { protected: - cmd_line_error(const std::string_view& base) : std::runtime_error{ message(base) } {} + cmd_line_error(const std::string_view& base) : std::runtime_error{ format_message(base) } {} cmd_line_error(const std::string_view& base, const std::string& details) : - std::runtime_error{ message(base, details) }, + std::runtime_error{ format_message(base, details) }, details_{ details } {} const std::string& details() const noexcept @@ -118,10 +111,10 @@ class invalid_option_value : public cmd_line_error { class file_error : public std::runtime_error { public: - file_error(const std::string& base) : std::runtime_error{ message(base) } {} + file_error(const std::string& base) : std::runtime_error{ format_message(base) } {} file_error(const std::string& base, const std::filesystem::path& filepath) : - std::runtime_error{ message(base, filepath.string()) }, + std::runtime_error{ format_message(base, filepath.string()) }, filepath_{ filepath } {} const std::filesystem::path& filepath() const noexcept diff --git a/test/utils.cpp b/test/utils.cpp index 62f00b6..f95d48d 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -24,3 +24,14 @@ void replace_all(std::string& str, const std::string& substr, const std::string& for (; pos != std::string::npos; pos = str.find(substr, pos + new_substr_size)) str.replace(pos, substr_size, new_substr); } + + +//combine a 2-part message to one message +std::string format_message(const std::string_view& base, const std::string& extra) +{ + std::string msg{ base }; + if (!extra.empty()) + msg += ": " + extra; + + return msg; +} diff --git a/test/utils.h b/test/utils.h index dd95172..846df98 100644 --- a/test/utils.h +++ b/test/utils.h @@ -20,6 +20,8 @@ void replace_all(std::string& str, const std::string& substr, const std::string& new_substr); +std::string format_message(const std::string_view& base, const std::string& extra = ""); + //split a delimited string or string_view into vector template std::vector split(const T& s, char delimiter) From ee8b6956c483391775e9e8ad11cf6ee295864df3 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 20:27:38 -0400 Subject: [PATCH 39/42] Enhance custom error class --- test/suites.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/suites.h b/test/suites.h index 9f09db0..1392774 100644 --- a/test/suites.h +++ b/test/suites.h @@ -18,6 +18,8 @@ #include #include +#include "utils.h" + using suite_runner_type = void(*)(); using suites_map_type = std::unordered_map; @@ -28,9 +30,16 @@ const suites_map_type& get_test_suites(); class test_suite_add_error : public std::runtime_error { public: - test_suite_add_error(const std::string& base, const std::string& name) - : std::runtime_error{ base + ": " + name } {} + test_suite_add_error(const std::string& base, const std::string& suite_name) + : std::runtime_error{ format_message(base, suite_name) }, suite_name_{ suite_name } {} + + const std::string& suite_name() const noexcept + { + return suite_name_; + } +private: + std::string suite_name_; }; From df45ae275293927fa81636aae17e485e14390bdc Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 20:27:54 -0400 Subject: [PATCH 40/42] Reject empty test-suite name --- test/suites.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/suites.cpp b/test/suites.cpp index 79fd514..2633fd3 100644 --- a/test/suites.cpp +++ b/test/suites.cpp @@ -22,9 +22,11 @@ static suites_map_type test_suites; //add an entry to the suites collection, throwing an appropriate exception if insertion fails -static void checked_insert(suites_map_type& suites, const std::string& name, suite_runner_type runner) +static void insert_suite(suites_map_type& suites, const std::string& name, suite_runner_type runner) { - if (!suites.insert({ name, runner }).second) { + if (name.empty()) + throw test_suite_add_error("empty test-suite name", ""); + else if (!suites.insert({ name, runner }).second) { if (suites.find(name) == suites.end()) throw test_suite_add_error("unknown error adding test suite", name); else @@ -53,15 +55,17 @@ void build_suites_collection() \ //macro to declare a suite runner function and to add it to the suites collection //Example: TEST_SUITE(array_test) adds the following two lines of code: // void array_test(); -// checked_insert(test_suites, "array_test", array_test; +// insert_suite(test_suites, "array_test", array_test; #define TEST_SUITE(SUITE_NAME) \ void SUITE_NAME(); \ - checked_insert(test_suites, #SUITE_NAME, SUITE_NAME); + insert_suite(test_suites, #SUITE_NAME, SUITE_NAME); + //flag to denote if suites collection is already built static bool collection_not_built{ true }; -//provide read-only access to collection of all test suites defined + +//provide read-only access to the collection of all test suites defined //build collection "just in time" if it has not already been built const std::unordered_map& get_test_suites() { @@ -80,8 +84,8 @@ const std::unordered_map& get_test_suites() START_SUITES_COLLECTION //same as void build_suites_collection() { - //add one line per test suite: macro parameter should be the name of a suite runner function - //the semi-colon at the end of macro invocation is not required but its use make things look authentic + //add one line per test suite: macro parameter should be the name of a suite-runner function + //the semi-colon at the end of macro invocation is not required but its use make things look "authentic" TEST_SUITE(array_test); From 39d12426818fa6dfb7741f00a2b311c76a132fda Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 20:56:08 -0400 Subject: [PATCH 41/42] Add enum for error code; print error code in show_error --- test/driver.cpp | 53 +++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index 38ed67d..f603e96 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -26,12 +26,23 @@ #include "options.h" #include "options-exceptions.h" +//error codes returned back from main are negative +enum class error_code { + cmd_line_initial = -1, file_initial = -2, unexpected_typed_initial = -3, unexpected_untyped_initial = -4, + cmd_line_run = -6, file_run = -7, suite_add_run = -8, unexpected_typed_run = -9, + unexpected_untyped_run = -10 +}; + void run_suites(const Options& options); -static void show_error(const char* message); +static int show_error(const char* message, error_code ec); static void show_usage(const char* program_path); -static void show_error_and_usage(const char* message, const char* program_path); +static int show_error_and_usage(const char* message, const char* program_path, error_code ec); + +//return < 0: error +//return == 0: no error, no tests failed +//return > 0: no error, tests failed; number of failed tests returned int main(int argc, char* argv[]) { Options options; @@ -42,20 +53,17 @@ int main(int argc, char* argv[]) apply_options(options, fileOut); } catch (const cmd_line_error& cle) { - show_error_and_usage(cle.what(), argv[0]); - return -1; + return show_error_and_usage(cle.what(), argv[0], error_code::cmd_line_initial); } catch (const file_error& fe) { - show_error_and_usage(fe.what(), argv[0]); - return -2; + return show_error_and_usage(fe.what(), argv[0], error_code::file_initial); } catch (const std::exception& e) { - show_error((std::string{ "Unexpected error: " } +e.what()).data()); - return -3; + auto message = format_message("Unexpected error", e.what()); + return show_error(message.data(), error_code::unexpected_typed_initial); } catch (...) { - show_error("Unexpected error"); - return -4; + show_error("Unexpected error", error_code::unexpected_untyped_initial); } @@ -63,20 +71,17 @@ int main(int argc, char* argv[]) run_suites(options); } catch (const cmd_line_error& cle) { - show_error_and_usage(cle.what(), argv[0]); - return -5; + show_error_and_usage(cle.what(), argv[0], error_code::cmd_line_run); } catch (const test_suite_add_error& tae) { - show_error(tae.what()); - return -6; + show_error(tae.what(), error_code::suite_add_run); } catch (const std::exception& e) { - show_error((std::string{ "Unexpected error: " } +e.what()).data()); - return -7; + auto message = format_message("Unexpected error", e.what()); + show_error(message.data(), error_code::unexpected_typed_run); } catch (...) { - show_error("Unexpected error"); - return -8; + show_error("Unexpected error", error_code::unexpected_untyped_run); } if (options.summary) @@ -128,17 +133,21 @@ void run_suites(const Options& options) //the following functions assume they are called only from main so that the parameters are always correct //assertion and error handling are included by design -static void show_error_and_usage(const char* message, const char* program_path) +static int show_error_and_usage(const char* message, const char* program_path, error_code ec) { - show_error(message); + show_error(message, ec); std::cout << '\n'; show_usage(program_path); + + return static_cast(ec); } -static void show_error(const char* message) +static int show_error(const char* message, error_code ec) { - std::cout << message << '\n'; + int code{ static_cast(ec) }; + std::cout << "Error " << code << "; " << message << '\n'; + return code; } From d2efc8786122b961d34e3ac9bb4c497fad53bf92 Mon Sep 17 00:00:00 2001 From: Sean Murthy Date: Sat, 13 Jun 2020 20:59:01 -0400 Subject: [PATCH 42/42] Fix comment --- test/driver.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/driver.cpp b/test/driver.cpp index f603e96..d151e56 100644 --- a/test/driver.cpp +++ b/test/driver.cpp @@ -29,7 +29,7 @@ //error codes returned back from main are negative enum class error_code { cmd_line_initial = -1, file_initial = -2, unexpected_typed_initial = -3, unexpected_untyped_initial = -4, - cmd_line_run = -6, file_run = -7, suite_add_run = -8, unexpected_typed_run = -9, + cmd_line_run = -6, file_run = -7, suite_add_run = -8, unexpected_typed_run = -9, unexpected_untyped_run = -10 }; @@ -103,7 +103,7 @@ void run_suites(const Options& options) auto run_all_suites = names_to_run.empty(); //check that the suite names specified in options correspond to suites defined - //this check is not required to run the suites, but is included to inform the user of any issues; + //this check is not required to run the suites, but is included to inform the user of any issues //silently ignoring an unfound suite leaves the user unaware of the reason the suite doesn't run if (!run_all_suites) { auto end_suites = suites.cend(); @@ -146,7 +146,7 @@ static int show_error_and_usage(const char* message, const char* program_path, e static int show_error(const char* message, error_code ec) { int code{ static_cast(ec) }; - std::cout << "Error " << code << "; " << message << '\n'; + std::cout << "Error " << code << "; " << message << '\n'; return code; }