temp
This commit is contained in:
2
BUILD
2
BUILD
@@ -4,7 +4,7 @@ cc_library(
|
|||||||
name = "tinytest",
|
name = "tinytest",
|
||||||
srcs = ["tinytest.cpp"],
|
srcs = ["tinytest.cpp"],
|
||||||
hdrs = ["tinytest.h"],
|
hdrs = ["tinytest.h"],
|
||||||
includes = ["."],
|
includes = ["*.h"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
83
tinytest.cpp
83
tinytest.cpp
@@ -64,78 +64,78 @@ TestResults::TestResults(uint32_t errors,
|
|||||||
skipped_(skipped),
|
skipped_(skipped),
|
||||||
total_(total) {}
|
total_(total) {}
|
||||||
|
|
||||||
TestResults& TestResults::error() {
|
TestResults& TestResults::Error() {
|
||||||
errors_++;
|
errors_++;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResults& TestResults::error(string message) {
|
TestResults& TestResults::Error(string message) {
|
||||||
errors_++;
|
errors_++;
|
||||||
error_messages_.push_back(message);
|
error_messages_.push_back(message);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResults& TestResults::fail() {
|
TestResults& TestResults::Fail() {
|
||||||
total_++;
|
total_++;
|
||||||
failed_++;
|
failed_++;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResults& TestResults::fail(const string& message) {
|
TestResults& TestResults::Fail(const string& message) {
|
||||||
total_++;
|
total_++;
|
||||||
failed_++;
|
failed_++;
|
||||||
failure_messages_.push_back(message);
|
failure_messages_.push_back(message);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> TestResults::failure_messages() {
|
vector<string> TestResults::FailureMessages() const {
|
||||||
return failure_messages_;
|
return failure_messages_;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResults& TestResults::pass() {
|
TestResults& TestResults::Pass() {
|
||||||
total_++;
|
total_++;
|
||||||
passed_++;
|
passed_++;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResults& TestResults::skip() {
|
TestResults& TestResults::Skip() {
|
||||||
total_++;
|
total_++;
|
||||||
skipped_++;
|
skipped_++;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResults& TestResults::skip(const string& message) {
|
TestResults& TestResults::Skip(const string& message) {
|
||||||
total_++;
|
total_++;
|
||||||
skipped_++;
|
skipped_++;
|
||||||
skip_messages_.push_back(message);
|
skip_messages_.push_back(message);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> TestResults::skip_messages() {
|
vector<string> TestResults::SkipMessages() const {
|
||||||
return skip_messages_;
|
return skip_messages_;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> TestResults::error_messages() {
|
vector<string> TestResults::ErrorMessages() const {
|
||||||
return error_messages_;
|
return error_messages_;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t TestResults::errors() {
|
uint32_t TestResults::Errors() const {
|
||||||
return errors_;
|
return errors_;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t TestResults::failed() {
|
uint32_t TestResults::Failed() const {
|
||||||
return failed_;
|
return failed_;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t TestResults::passed() {
|
uint32_t TestResults::Passed() const {
|
||||||
return passed_;
|
return passed_;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t TestResults::skipped() {
|
uint32_t TestResults::Skipped() const {
|
||||||
return skipped_;
|
return skipped_;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t TestResults::total() {
|
uint32_t TestResults::Total() const {
|
||||||
return total_;
|
return total_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,32 +173,32 @@ TestResults& TestResults::operator+=(const TestResults& other) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PrintResults(std::ostream& os, TestResults results) {
|
void PrintResults(std::ostream& os, TestResults results) {
|
||||||
auto skip_messages = results.skip_messages();
|
auto skip_messages = results.SkipMessages();
|
||||||
if (skip_messages.size() > 0) {
|
if (skip_messages.size() > 0) {
|
||||||
os << "Skipped:" << endl;
|
os << "Skipped:" << endl;
|
||||||
for_each(skip_messages.begin(), skip_messages.end(), [&os](const string& message) {
|
for_each(skip_messages.begin(), skip_messages.end(), [&os](const string& message) {
|
||||||
os << "🚧Skipped: " << message << endl;
|
os << "🚧Skipped: " << message << endl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
auto failure_messages = results.failure_messages();
|
auto failure_messages = results.FailureMessages();
|
||||||
if (failure_messages.size() > 0) {
|
if (failure_messages.size() > 0) {
|
||||||
os << "Failures:" << endl;
|
os << "Failures:" << endl;
|
||||||
for_each(failure_messages.begin(), failure_messages.end(), [&os](const string& message) {
|
for_each(failure_messages.begin(), failure_messages.end(), [&os](const string& message) {
|
||||||
os << "❌FAILED: " << message << endl;
|
os << "❌FAILED: " << message << endl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
auto error_messages = results.error_messages();
|
auto error_messages = results.ErrorMessages();
|
||||||
if (error_messages.size() > 0) {
|
if (error_messages.size() > 0) {
|
||||||
os << "Errors:" << endl;
|
os << "Errors:" << endl;
|
||||||
for_each(error_messages.begin(), error_messages.end(), [&os](const string& message) {
|
for_each(error_messages.begin(), error_messages.end(), [&os](const string& message) {
|
||||||
os << "🔥ERROR: " << message << endl;
|
os << "🔥ERROR: " << message << endl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
os << "Total tests: " << results.total() << endl;
|
os << "Total tests: " << results.Total() << endl;
|
||||||
os << "Passed: " << results.passed() << " ✅" << endl;
|
os << "Passed: " << results.Passed() << " ✅" << endl;
|
||||||
os << "Failed: " << results.failed() << " ❌" << endl;
|
os << "Failed: " << results.Failed() << " ❌" << endl;
|
||||||
os << "Skipped: " << results.skipped() << " 🚧" << endl;
|
os << "Skipped: " << results.Skipped() << " 🚧" << endl;
|
||||||
os << "Errors: " << results.errors() << " 🔥" << endl;
|
os << "Errors: " << results.Errors() << " 🔥" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// End TestResults methods.
|
// End TestResults methods.
|
||||||
@@ -206,4 +206,41 @@ void PrintResults(std::ostream& os, TestResults results) {
|
|||||||
MaybeTestConfigureFunction DefaultTestConfigureFunction() {
|
MaybeTestConfigureFunction DefaultTestConfigureFunction() {
|
||||||
return std::nullopt;
|
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<const std::string> 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
|
} // namespace TinyTest
|
||||||
|
|||||||
527
tinytest.h
527
tinytest.h
@@ -10,58 +10,154 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <initializer_list>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// TODO: Document this.
|
namespace TinyTest {
|
||||||
// Tuple printer from:
|
// Begin EscapeForPrinting
|
||||||
// https://stackoverflow.com/questions/6245735/pretty-print-stdtuple/31116392#58417285
|
template <typename TChar, typename TTraits>
|
||||||
template <typename TChar, typename TTraits, typename... TArgs>
|
std::basic_string_view<TChar, TTraits> EscapeForPrinting(const std::basic_string_view<TChar, TTraits>& text) {
|
||||||
auto& operator<<(std::basic_ostream<TChar, TTraits>& os, std::tuple<TArgs...> const& t) {
|
return std::regex_replace(text, std::regex("\033"), "\\033");
|
||||||
std::apply([&os](auto&&... args) { ((os << args << " "), ...); }, t);
|
}
|
||||||
|
|
||||||
|
template <typename TChar, typename TTraits>
|
||||||
|
std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string<TChar, TTraits>& text) {
|
||||||
|
return std::regex_replace(text, std::regex("\033"), "\\033");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TChar>
|
||||||
|
std::basic_string<TChar> EscapeForPrinting(const TChar* text) {
|
||||||
|
return std::regex_replace(text, std::regex("\033"), "\\033");
|
||||||
|
}
|
||||||
|
|
||||||
|
// End EscapeForPrinting
|
||||||
|
|
||||||
|
// Begin PrettyPrint
|
||||||
|
// std::string_view.
|
||||||
|
template <typename TChar, typename TTraits>
|
||||||
|
auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::basic_string_view<TChar, TTraits>& item) {
|
||||||
|
os << "\"" << EscapeForPrinting(item) << "\"";
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Document this.
|
// std::string.
|
||||||
template <typename TChar, typename TTraits, typename TItem>
|
template <typename TChar, typename TTraits>
|
||||||
auto& operator<<(std::basic_ostream<TChar, TTraits>& os, std::vector<TItem> v) {
|
auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::basic_string<TChar, TTraits>& item) {
|
||||||
|
os << "\"" << EscapeForPrinting(item) << "\"";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const char*.
|
||||||
|
template <typename TChar, typename TTraits>
|
||||||
|
auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const TChar* item) {
|
||||||
|
os << "\"" << EscapeForPrinting(item) << "\"";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tuple<...>
|
||||||
|
template <typename TChar, typename TTraits, typename... TArgs>
|
||||||
|
auto& PrettyPrint(std::basic_ostream<TChar, TTraits> os, const std::tuple<TArgs...>& tuple) {
|
||||||
|
std::apply(
|
||||||
|
[&os](auto&&... args) {
|
||||||
|
if (sizeof...(TArgs) == 0) {
|
||||||
|
os << "[]";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t n = 0;
|
||||||
os << "[ ";
|
os << "[ ";
|
||||||
for (auto it = v.begin(); it != v.end(); it++) {
|
((PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? ", " : "")), ...);
|
||||||
if (it != v.begin()) {
|
os << " ]";
|
||||||
|
},
|
||||||
|
tuple);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
// containers
|
||||||
|
template <typename TChar,
|
||||||
|
typename TTraits,
|
||||||
|
typename TContainer,
|
||||||
|
typename = std::enable_if_t<
|
||||||
|
std::is_same_v<decltype(std::declval<TContainer>().begin()), decltype(std::declval<TContainer>().end())>>,
|
||||||
|
typename = std::enable_if_t<std::is_base_of_v<
|
||||||
|
std::input_iterator_tag,
|
||||||
|
typename std::iterator_traits<decltype(std::declval<TContainer>().begin())>::iterator_category>>>
|
||||||
|
auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, TContainer container) {
|
||||||
|
os << "[ ";
|
||||||
|
for (auto it = container.begin(); it != container.end(); it++) {
|
||||||
|
if (it != container.begin()) {
|
||||||
os << ", ";
|
os << ", ";
|
||||||
}
|
}
|
||||||
os << *it;
|
PrettyPrint(os, *it);
|
||||||
}
|
}
|
||||||
os << " ]";
|
os << " ]";
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Document this.
|
// Catch-all for everything else.
|
||||||
template <typename TChar, typename TTraits, typename TItem>
|
template <typename TChar, typename TTraits, typename TItem>
|
||||||
auto& compare(std::basic_ostream<TChar, TTraits>& error_message,
|
auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, TItem item) {
|
||||||
std::vector<TItem> expected,
|
os << item;
|
||||||
std::vector<TItem> actual) {
|
return os;
|
||||||
if (expected.size() != actual.size()) {
|
}
|
||||||
error_message << "size mismatch expected: " << expected.size() << ", actual: " << actual.size();
|
|
||||||
return error_message;
|
// End PrettyPrint
|
||||||
}
|
|
||||||
|
// Begin PrettyPrintWithSeparator
|
||||||
for (size_t index = 0; index < expected.size(); index++) {
|
// Prints args with separator between them. std::string_view separator.
|
||||||
if (expected[index] != actual[index]) {
|
template <typename TChar, typename TTraits, typename... TArgs>
|
||||||
error_message << "vectors differ at index " << index << ", \"" << expected[index] << "\" != \"" << actual[index]
|
auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os,
|
||||||
<< "\", expected: \"" << expected << "\", actual: \"" << actual << "\"";
|
std::basic_string_view<TChar, TTraits> separator,
|
||||||
return error_message;
|
TArgs&&... args) {
|
||||||
}
|
(((PrettyPrint(os, args), os), EscapeForPrinting(separator)), ...);
|
||||||
}
|
return os;
|
||||||
return error_message;
|
}
|
||||||
|
|
||||||
|
// Prints args with separator between them. std::string separator.
|
||||||
|
template <typename TChar, typename TTraits, typename... TArgs>
|
||||||
|
auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os,
|
||||||
|
std::basic_string<TChar, TTraits> separator,
|
||||||
|
TArgs&&... args) {
|
||||||
|
(((PrettyPrint_item(os, args), os), EscapeForPrinting(separator)), ...);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints args with separator between them. const char* separator.
|
||||||
|
template <typename TChar, typename TTraits, typename... Args>
|
||||||
|
auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os, const TChar* separator, Args&&... args) {
|
||||||
|
((os << args << EscapeForPrinting(separator)), ...);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End PrettyPrintWithSeparator
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
|
||||||
|
// TODO: Document this.
|
||||||
|
template <typename TResult, typename... TParameters>
|
||||||
|
std::string InterceptCout(std::function<TResult(TParameters...)> fnToExecute,
|
||||||
|
std::optional<std::tuple<TParameters...>> 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
|
/// @brief
|
||||||
class TestResults {
|
class TestResults {
|
||||||
public:
|
public:
|
||||||
@@ -93,69 +189,69 @@ class TestResults {
|
|||||||
|
|
||||||
/// @brief Adds an error. This increments errors.
|
/// @brief Adds an error. This increments errors.
|
||||||
/// @return A reference to this instance. Used for chaining.
|
/// @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
|
/// @brief Adds an error with a message. This increments errors as well as
|
||||||
/// saving the error message.
|
/// saving the error message.
|
||||||
/// @param message The error message.
|
/// @param message The error message.
|
||||||
/// @return A reference to this instance. Used for chaining.
|
/// @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.
|
/// @brief Adds a failed test. This increments total and failed.
|
||||||
/// @return A reference to this instance. Used for chaining.
|
/// @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
|
/// @brief Adds a failed test with a message. This increments total and failed
|
||||||
/// as well as saving the failure message.
|
/// as well as saving the failure message.
|
||||||
/// @param message The reason the test failed.
|
/// @param message The reason the test failed.
|
||||||
/// @return A reference to this instance. Used for chaining.
|
/// @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.
|
/// @brief Adds a passed test. This increments total and passed.
|
||||||
/// @return A reference to this instance. Used for chaining.
|
/// @return A reference to this instance. Used for chaining.
|
||||||
TestResults& pass();
|
TestResults& Pass();
|
||||||
|
|
||||||
/// @brief Adds a skipped test. This increments total and skipped.
|
/// @brief Adds a skipped test. This increments total and skipped.
|
||||||
/// @return A reference to this instance. Used for chaining.
|
/// @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
|
/// @brief Adds a skipped test with a message. This increments total and
|
||||||
/// skipped as well as saving the skip message.
|
/// skipped as well as saving the skip message.
|
||||||
/// @param message The reason the test was skipped.
|
/// @param message The reason the test was skipped.
|
||||||
/// @return A reference to this instance. Used for chaining.
|
/// @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.
|
/// @brief Getter for the list of error messages.
|
||||||
/// @return
|
/// @return
|
||||||
std::vector<std::string> error_messages();
|
std::vector<std::string> ErrorMessages() const;
|
||||||
|
|
||||||
/// @brief Getter for the count of errors.
|
/// @brief Getter for the count of errors.
|
||||||
/// @return
|
/// @return
|
||||||
uint32_t errors();
|
uint32_t Errors() const;
|
||||||
|
|
||||||
/// @brief Getter for the count of failed tests.
|
/// @brief Getter for the count of failed tests.
|
||||||
/// @return 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.
|
/// @brief Getter for the list of failure messages.
|
||||||
/// @return The list of failure messages.
|
/// @return The list of failure messages.
|
||||||
std::vector<std::string> failure_messages();
|
std::vector<std::string> FailureMessages() const;
|
||||||
|
|
||||||
/// @brief Getter for the count of passed tests.
|
/// @brief Getter for the count of passed tests.
|
||||||
/// @return 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.
|
/// @brief Getter for the count of skipped tests.
|
||||||
/// @return 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.
|
/// @brief Getter for the list of skip messages.
|
||||||
/// @return The list of skip messages.
|
/// @return The list of skip messages.
|
||||||
std::vector<std::string> skip_messages();
|
std::vector<std::string> SkipMessages() const;
|
||||||
|
|
||||||
/// @brief Getter for the count of total tests.
|
/// @brief Getter for the count of total tests.
|
||||||
/// @return The count of total tests run.
|
/// @return The count of total tests run.
|
||||||
uint32_t total();
|
uint32_t Total() const;
|
||||||
|
|
||||||
/// @brief Returns the combination of this and another TestResults instance.
|
/// @brief Returns the combination of this and another TestResults instance.
|
||||||
/// @param other The other TestResults instance to add to this one.
|
/// @param other The other TestResults instance to add to this one.
|
||||||
@@ -214,8 +310,8 @@ using TestTuple =
|
|||||||
std::tuple<TInputParams...> /* input_params - The input parameters for this test. These will be used when
|
std::tuple<TInputParams...> /* input_params - The input parameters for this test. These will be used when
|
||||||
calling std::apply with function_to_test to execute the test. */
|
calling std::apply with function_to_test to execute the test. */
|
||||||
,
|
,
|
||||||
MaybeTestCompareFunction<TResult> /* test_compare_function - If this is not nullprt then this function
|
MaybeTestCompareFunction<TResult> /* test_Compare_function - If this is not nullprt then this function
|
||||||
will be called instead of suite_compare_function to determine if the
|
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
|
test passes. Use this to check for side effects of the test. Return
|
||||||
true if the test passes and false otherwise. */
|
true if the test passes and false otherwise. */
|
||||||
,
|
,
|
||||||
@@ -236,89 +332,17 @@ using TestTuple =
|
|||||||
template <typename TResult, typename... TInputParams>
|
template <typename TResult, typename... TInputParams>
|
||||||
using TestSuite = std::tuple<std::string,
|
using TestSuite = std::tuple<std::string,
|
||||||
std::function<TResult(TInputParams...)>,
|
std::function<TResult(TInputParams...)>,
|
||||||
std::vector<TestTuple<TResult, TInputParams...>>,
|
std::initializer_list<TestTuple<TResult, TInputParams...>>,
|
||||||
MaybeTestCompareFunction<TResult>,
|
MaybeTestCompareFunction<TResult>,
|
||||||
MaybeTestConfigureFunction,
|
MaybeTestConfigureFunction,
|
||||||
MaybeTestConfigureFunction,
|
MaybeTestConfigureFunction,
|
||||||
bool>;
|
bool>;
|
||||||
|
|
||||||
// This function is called to execute a test suite. You provide it with some
|
TestResults& SkipTest(TestResults& results,
|
||||||
// configuration info, optional utility callback functions, and test data (input
|
const std::string& suite_label,
|
||||||
// parameters for each call to function_to_test and the expected result). It
|
const std::string& test_label,
|
||||||
// returns a TestResults that should be treated as an opaque data type. Not all
|
std::optional<const std::string> reason = std::nullopt);
|
||||||
// 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<tuple<...>>
|
|
||||||
// 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.
|
|
||||||
/// @brief
|
/// @brief
|
||||||
/// @tparam TResult The result type of the test.
|
/// @tparam TResult The result type of the test.
|
||||||
/// @tparam TInputParams... The types of parameters sent to the test function.
|
/// @tparam TInputParams... The types of parameters sent to the test function.
|
||||||
@@ -327,25 +351,36 @@ using TestSuite = std::tuple<std::string,
|
|||||||
/// @param function_to_test The function to be tested. It will be called with
|
/// @param function_to_test The function to be tested. It will be called with
|
||||||
/// std::apply and a std::tuple<TInputParams...> made from each item in tests.
|
/// std::apply and a std::tuple<TInputParams...> made from each item in tests.
|
||||||
/// @param tests A std::vector of test runs.
|
/// @param tests A std::vector of test runs.
|
||||||
/// @param suite_compare_function A function used to compare the expected and
|
/// @param suite_Compare A function used to Compare the expected and actual test
|
||||||
/// actual test results. This can be overridden per test by setting
|
/// results. This can be overridden per test by setting test_Compare.
|
||||||
/// test_compare_function.
|
|
||||||
/// @param after_all This is called before each suite is started to setup the
|
/// @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
|
/// environment. This is where you should build mocks, setup spies, and test
|
||||||
/// fixtures.
|
/// fixtures.
|
||||||
/// @param before_all This is called after each suite has completed to cleanup
|
/// @param before_all This is called after each suite has completed to cleanup
|
||||||
/// anything allocated in suite_before_each.
|
/// anything allocated in suite_before_each.
|
||||||
/// @param is_enabled If false none of these tests are run and they are all
|
/// @param is_enabled If false the test is reported as skipped. If true the test
|
||||||
/// reported as skipped.
|
/// is run as normal.
|
||||||
template <typename TResult, typename... TInputParams>
|
template <typename TResult, typename... TInputParams>
|
||||||
TestResults execute_suite(std::string suite_label,
|
TestResults ExecuteSuite(std::string suite_label,
|
||||||
std::function<TResult(TInputParams...)> function_to_test,
|
std::function<TResult(TInputParams...)> function_to_test,
|
||||||
std::vector<TestTuple<TResult, TInputParams...>> tests,
|
std::initializer_list<TestTuple<TResult, TInputParams...>> tests,
|
||||||
MaybeTestCompareFunction<TResult> suite_compare = std::nullopt,
|
MaybeTestCompareFunction<TResult> suite_Compare = std::nullopt,
|
||||||
MaybeTestConfigureFunction before_all = std::nullopt,
|
MaybeTestConfigureFunction before_all = std::nullopt,
|
||||||
MaybeTestConfigureFunction after_all = std::nullopt,
|
MaybeTestConfigureFunction after_all = std::nullopt,
|
||||||
bool is_enabled = true) {
|
bool is_enabled = true) {
|
||||||
TestResults results;
|
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;
|
std::cout << "🚀Beginning Suite: " << suite_label << std::endl;
|
||||||
|
|
||||||
// Step 1: Suite Setup
|
// Step 1: Suite Setup
|
||||||
@@ -357,29 +392,28 @@ TestResults execute_suite(std::string suite_label,
|
|||||||
// Step 2: Execute Tests
|
// Step 2: Execute Tests
|
||||||
for_each(tests.begin(),
|
for_each(tests.begin(),
|
||||||
tests.end(),
|
tests.end(),
|
||||||
[&suite_label, &function_to_test, &results, &suite_compare](TestTuple<TResult, TInputParams...> test_data) {
|
[&suite_label, &function_to_test, &results, &suite_Compare](TestTuple<TResult, TInputParams...> test_data) {
|
||||||
// Step 2a: Extract our variables from the TestTuple.
|
// Step 2a: Extract our variables from the TestTuple.
|
||||||
const std::string& test_name = std::get<0>(test_data);
|
const std::string& test_label = std::get<0>(test_data);
|
||||||
const std::string qualified_test_name = suite_label + "::" + test_name;
|
const std::string qualified_test_label = suite_label + "::" + test_label;
|
||||||
const TResult& expected_output = std::get<1>(test_data);
|
const TResult& expected_output = std::get<1>(test_data);
|
||||||
std::tuple<TInputParams...> input_params = std::get<2>(test_data);
|
std::tuple<TInputParams...> input_params = std::get<2>(test_data);
|
||||||
MaybeTestCompareFunction<TResult> maybe_compare_function = std::get<3>(test_data);
|
MaybeTestCompareFunction<TResult> maybe_Compare_function = std::get<3>(test_data);
|
||||||
TestCompareFunction<TResult> compare_function =
|
TestCompareFunction<TResult> Compare_function =
|
||||||
maybe_compare_function.has_value() ? *maybe_compare_function
|
maybe_Compare_function.has_value() ? *maybe_Compare_function
|
||||||
: suite_compare.has_value() ? *suite_compare
|
: suite_Compare.has_value() ? *suite_Compare
|
||||||
: [](const TResult& l, const TResult& r) { return l == r; };
|
: [](const TResult& l, const TResult& r) { return l == r; };
|
||||||
MaybeTestConfigureFunction before_each = std::get<4>(test_data);
|
MaybeTestConfigureFunction before_each = std::get<4>(test_data);
|
||||||
MaybeTestConfigureFunction after_each = std::get<5>(test_data);
|
MaybeTestConfigureFunction after_each = std::get<5>(test_data);
|
||||||
bool is_enabled = std::get<6>(test_data);
|
bool is_enabled = std::get<6>(test_data);
|
||||||
|
|
||||||
if (!is_enabled) {
|
if (!is_enabled) {
|
||||||
std::cout << " 🚧Skipping Test: " << test_name << std::endl;
|
SkipTest(results, suite_label, test_label);
|
||||||
results.skip(qualified_test_name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2b: Test Setup
|
// 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()) {
|
if (before_each.has_value()) {
|
||||||
(*before_each)();
|
(*before_each)();
|
||||||
}
|
}
|
||||||
@@ -390,30 +424,35 @@ TestResults execute_suite(std::string suite_label,
|
|||||||
actual = std::apply(function_to_test, input_params);
|
actual = std::apply(function_to_test, input_params);
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
os << "Caught exception \"" << ex.what() << "\"";
|
os << "Caught exception \"" << ex.what() << "\".";
|
||||||
results.error(qualified_test_name + " " + os.str());
|
results.Error(qualified_test_label + " " + os.str());
|
||||||
std::cout << " 🔥ERROR: " << os.str() << std::endl;
|
std::cout << " 🔥ERROR: " << os.str() << std::endl;
|
||||||
} catch (const std::string& message) {
|
} catch (const std::string& message) {
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
os << "Caught string \"" << message << "\"";
|
os << "Caught string \"" << message << "\".";
|
||||||
results.error(qualified_test_name + " " + os.str());
|
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;
|
std::cout << " 🔥ERROR: " << os.str() << std::endl;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::string message =
|
std::string message =
|
||||||
"Caught something that is neither an std::exception "
|
"Caught something that is neither an std::exception "
|
||||||
"nor an std::string.";
|
"nor an std::string.";
|
||||||
results.error(qualified_test_name + " " + message);
|
results.Error(qualified_test_label + " " + message);
|
||||||
std::cout << " 🔥ERROR: " << message << std::endl;
|
std::cout << " 🔥ERROR: " << message << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2d: Pass or fail.
|
// Step 2d: Pass or fail.
|
||||||
if (compare_function(expected_output, actual)) {
|
if (Compare_function(expected_output, actual)) {
|
||||||
results.pass();
|
results.Pass();
|
||||||
std::cout << " ✅PASSED" << std::endl;
|
std::cout << " ✅PASSED" << std::endl;
|
||||||
} else {
|
} else {
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
os << "expected: \"" << expected_output << "\", actual: \"" << actual << "\"";
|
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;
|
std::cout << " ❌FAILED: " << os.str() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +460,7 @@ TestResults execute_suite(std::string suite_label,
|
|||||||
if (after_each.has_value()) {
|
if (after_each.has_value()) {
|
||||||
(*after_each)();
|
(*after_each)();
|
||||||
}
|
}
|
||||||
std::cout << " Ending Test: " << test_name << std::endl;
|
std::cout << " Ending Test: " << test_label << std::endl;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 3: Suite Teardown
|
// Step 3: Suite Teardown
|
||||||
@@ -435,30 +474,17 @@ TestResults execute_suite(std::string suite_label,
|
|||||||
/// @brief
|
/// @brief
|
||||||
/// @tparam TResult The result type of the test.
|
/// @tparam TResult The result type of the test.
|
||||||
/// @tparam TInputParams... The types of parameters sent to the test function.
|
/// @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
|
/// @param test_suite A tuple representing the test suite configuration.
|
||||||
/// such as "MortgageCalculator".
|
|
||||||
/// @param function_to_test The function to be tested. It will be called with
|
|
||||||
/// std::apply and a std::tuple<TInputParams...> 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.
|
|
||||||
template <typename TResult, typename... TInputParams>
|
template <typename TResult, typename... TInputParams>
|
||||||
TestResults execute_suite(std::string suite_label,
|
TestResults ExecuteSuite(const TestSuite<TResult, TInputParams...>& test_suite) {
|
||||||
std::function<TResult(TInputParams...)> function_to_test,
|
std::string suite_label = std::get<0>(test_suite);
|
||||||
std::initializer_list<TestTuple<TResult, TInputParams...>> tests,
|
std::function<TResult(TInputParams...)> function_to_test = std::get<1>(test_suite);
|
||||||
MaybeTestCompareFunction<TResult> suite_compare = std::nullopt,
|
std::initializer_list<TestTuple<TResult, TInputParams...>> tests = std::get<2>(test_suite);
|
||||||
MaybeTestConfigureFunction before_all = std::nullopt,
|
MaybeTestCompareFunction<TResult> suite_Compare = sizeof(test_suite) > 3 ? std::get<3>(test_suite) : std::nullopt;
|
||||||
MaybeTestConfigureFunction after_all = std::nullopt,
|
MaybeTestConfigureFunction before_all = sizeof(test_suite) > 4 ? std::get<4>(test_suite) : std::nullopt;
|
||||||
bool is_enabled = true) {
|
MaybeTestConfigureFunction after_all = sizeof(test_suite) > 5 ? std::get<5>(test_suite) : std::nullopt;
|
||||||
std::vector test_data = std::vector(tests);
|
bool is_enabled = sizeof(test_suite) > 6 ? std::get<6>(test_suite) : true;
|
||||||
return execute_suite(suite_label, function_to_test, tests, suite_compare, before_all, after_all, is_enabled);
|
return ExecuteSuite(suite_label, function_to_test, tests, suite_Compare, before_all, after_all, is_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief
|
/// @brief
|
||||||
@@ -470,7 +496,7 @@ TestResults execute_suite(std::string suite_label,
|
|||||||
/// input parameters.
|
/// input parameters.
|
||||||
/// @param input_params The input parameters to use when calling the test
|
/// @param input_params The input parameters to use when calling the test
|
||||||
/// function.
|
/// 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
|
/// expected and actual return values. This is good for when you only care about
|
||||||
/// certain fields being equal.
|
/// certain fields being equal.
|
||||||
/// @param before_each This is called to setup the environment before running
|
/// @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.
|
/// skipped for reporting purposes.
|
||||||
/// @return A TestTuple suitable for use as a test run when calling test_fn.
|
/// @return A TestTuple suitable for use as a test run when calling test_fn.
|
||||||
template <typename TResult, typename... TInputParams>
|
template <typename TResult, typename... TInputParams>
|
||||||
TestTuple<TResult, TInputParams...> make_test(const std::string& test_name,
|
TestTuple<TResult, TInputParams...> MakeTest(const std::string& test_name,
|
||||||
const TResult& expected,
|
const TResult& expected,
|
||||||
std::tuple<TInputParams...> input_params,
|
std::tuple<TInputParams...> input_params,
|
||||||
MaybeTestCompareFunction<TResult> test_compare_fn = std::nullopt,
|
MaybeTestCompareFunction<TResult> test_Compare_fn = std::nullopt,
|
||||||
MaybeTestConfigureFunction before_each = std::nullopt,
|
MaybeTestConfigureFunction before_each = std::nullopt,
|
||||||
MaybeTestConfigureFunction after_each = std::nullopt,
|
MaybeTestConfigureFunction after_each = std::nullopt,
|
||||||
bool is_enabled = true) {
|
bool is_enabled = true) {
|
||||||
return make_tuple(test_name, expected, input_params, test_compare_fn, before_each, after_each, is_enabled);
|
return make_tuple(test_name, expected, input_params, test_Compare_fn, before_each, after_each, is_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief
|
/// @brief
|
||||||
@@ -499,71 +525,114 @@ TestTuple<TResult, TInputParams...> make_test(const std::string& test_name,
|
|||||||
/// @param suite_name
|
/// @param suite_name
|
||||||
/// @param function_to_test
|
/// @param function_to_test
|
||||||
/// @param test_data
|
/// @param test_data
|
||||||
/// @param compare
|
/// @param Compare
|
||||||
/// @param before_each
|
/// @param before_each
|
||||||
/// @param after_each
|
/// @param after_each
|
||||||
/// @param is_enabled
|
/// @param is_enabled
|
||||||
/// @return
|
/// @return
|
||||||
template <typename TResult, typename TFunctionToTest, typename... TInputParams>
|
template <typename TResult, typename TFunctionToTest, typename... TInputParams>
|
||||||
TestSuite<TResult, TInputParams...> make_test_suite(const std::string& suite_name,
|
TestSuite<TResult, TInputParams...> MakeTestSuite(const std::string& suite_name,
|
||||||
TFunctionToTest function_to_test,
|
|
||||||
std::vector<TestTuple<TResult, TInputParams...>> test_data,
|
|
||||||
MaybeTestCompareFunction<TResult> 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 <typename TResult, typename TFunctionToTest, typename... TInputParams>
|
|
||||||
TestSuite<TResult, TInputParams...> make_test_suite(
|
|
||||||
const std::string& suite_name,
|
|
||||||
TFunctionToTest function_to_test,
|
TFunctionToTest function_to_test,
|
||||||
std::initializer_list<TestTuple<TResult, TInputParams...>> test_data,
|
std::initializer_list<TestTuple<TResult, TInputParams...>> test_data,
|
||||||
MaybeTestCompareFunction<TResult> compare = std::nullopt,
|
MaybeTestCompareFunction<TResult> Compare = std::nullopt,
|
||||||
MaybeTestConfigureFunction before_each = std::nullopt,
|
MaybeTestConfigureFunction before_each = std::nullopt,
|
||||||
MaybeTestConfigureFunction after_each = std::nullopt,
|
MaybeTestConfigureFunction after_each = std::nullopt,
|
||||||
bool is_enabled = true) {
|
bool is_enabled = true) {
|
||||||
return make_tuple(suite_name, function_to_test, test_data, compare, before_each, after_each, is_enabled);
|
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 <typename TResult, typename... TInputParams>
|
|
||||||
TestResults execute_suite(const TestSuite<TResult, TInputParams...>& test_suite) {
|
|
||||||
return execute_suite<TResult, TInputParams...>(
|
|
||||||
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 <typename... TInputParams>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Writes a friendly version of results to the provided stream.
|
/// @brief Writes a friendly version of results to the provided stream.
|
||||||
/// @param os The stream to write to.
|
/// @param os The stream to write to.
|
||||||
/// @param results The TestResults to write.
|
/// @param results The TestResults to write.
|
||||||
void PrintResults(std::ostream& os, TestResults results);
|
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 <typename TChar, typename TTraits, typename... TArgs>
|
||||||
|
auto& operator<<(std::basic_ostream<TChar, TTraits>& os, std::tuple<TArgs...> 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 <typename TChar, typename TTraits>
|
||||||
|
auto& operator<<(std::basic_ostream<TChar, TTraits>& os, const void* pointer) {
|
||||||
|
os << pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Simplify this.
|
||||||
|
template <typename TChar,
|
||||||
|
typename TTraits,
|
||||||
|
typename TContainer,
|
||||||
|
typename = std::enable_if_t<
|
||||||
|
std::is_same_v<decltype(std::declval<TContainer>().begin()), decltype(std::declval<TContainer>().end())>>,
|
||||||
|
typename = std::enable_if_t<std::is_base_of_v<
|
||||||
|
std::input_iterator_tag,
|
||||||
|
typename std::iterator_traits<decltype(std::declval<TContainer>().begin())>::iterator_category>>>
|
||||||
|
auto& operator<<(std::basic_ostream<TChar, TTraits>& 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 <typename TChar, typename TTraits, typename TItem>
|
||||||
|
auto& operator<<(std::basic_ostream<TChar, TTraits>& os, std::initializer_list<TItem> 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 <typename TChar, typename TTraits, typename TItem>
|
||||||
|
auto& Compare(std::basic_ostream<TChar, TTraits>& error_message,
|
||||||
|
std::vector<TItem> expected,
|
||||||
|
std::vector<TItem> 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
|
} // End namespace TinyTest
|
||||||
|
|
||||||
#endif // End !defined TEST_H__
|
#endif // End !defined TEST_H__
|
||||||
|
|||||||
1322
tinytest_test.cpp
1322
tinytest_test.cpp
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user