Skip to content

Instantly share code, notes, and snippets.

@eth-p
Last active March 10, 2018 07:12
Show Gist options
  • Save eth-p/bffaa1f5dd940ba5f8f34ec98b45e146 to your computer and use it in GitHub Desktop.
Save eth-p/bffaa1f5dd940ba5f8f34ec98b45e146 to your computer and use it in GitHub Desktop.
A (debatably) small C++ testing library.
#include "testy.h"
using namespace std;
using testy::Expect;
int main() {
// --------------------------------------------
// Basic Usage
// How to run a standalone test.
// --------------------------------------------
//
// testy::immediate(std::string name, std::function<void(testy::Expect)> lambda);
// --> Runs a test function.
//
// name : The test name.
// lambda : The test function.
//
testy::immediate("Running Tests", [](Expect expect) {
// Test code goes here.
});
// --------------------------------------------
// Creating Expectations
// How to actually test things.
// --------------------------------------------
//
// Expect<T>(T value)
// --> Creates an expectation for a value type.
//
// <T> : The expectation type. This can be inferred by the compiler.
// value : The unknown value to assert.
//
testy::immediate("Expectations", [](Expect expect) {
int unknown = /* do something that returns an int */;
expect(unknown); // Does nothing yet.
});
//
// Expect.function(std::function<void()> function)
// --> Creates an expectation for a function type.
//
// function : The function to assert.
//
testy::immediate("Your Expectations Are Running", [](Expect expect) {
int unknown = /* do something that returns an int */;
expect.function([] {
// Does nothing until asserted.
});
});
// --------------------------------------------
// Expectations
// The syntactic sugar of assertions.
// --------------------------------------------
//
// ValueExpectation<T>.equals(T value)
// --> Assert two values using the == operator.
//
// value : The expected value.
//
// NOTE -- The ==(T, T) operator must be implemented.
//
testy::immediate("Equality", [](Expect expect) {
int three = 3;
int five = 5;
expect(three).equals(five); // Test failed.
});
//
// Expectation<T>.never().[...]
// --> Negate an assertion.
//
// WARNING -- This is reset after each assertion.
//
testy::immediate("Negation", [](Expect expect) {
int three = 3;
int five = 5;
expect(three).never().equals(five); // Passed.
});
//
// Expectation<T>.that(std::string description).[...]
// --> Describe an assertion.
//
// description : The description.
//
testy::immediate("Descriptions", [](Expect expect) {
expect(2*2).that("Two times two is four.").equals(3);
});
//
// FunctionExpectation.couts(std::string value)
// --> Assert that the function prints a specific string to std::cout.
//
// value : The expected string.
//
testy::immediate("Couting", [](Expect expect) {
expect.function([] {
cout << "I'm sad\n";
}).couts("I'm happy\n"); // Test failed.
});
//
// FunctionExpectation.cerrs(std::string value)
// --> Assert that the function prints a specific string to std::cerr.
//
// value : The expected string.
//
testy::immediate("Couting, With Cerr", [](Expect expect) {
expect.function([] {
cerr << "This went wrong.\n";
}).cerrs(""); // Test failed.
});
//
// FunctionExpectation.throws<T>()
// --> Assert that the function throws a specific exception.
//
// <t> : The expected exception type.
// value : The expected string.
//
testy::immediate("Except", [](Expect expect) {
expect.function([] {
throw runtime_error("Javelin");
}).throws<runtime_error>(); // Test passed.
});
// --------------------------------------------
// Logging
// Where did it go wrong?
// --------------------------------------------
//
// testy will tell you how things went wrong.
// It's better than leaving a bunch of
// std::cout << statements everywhere.
//
testy::immediate("Oops", [](Expect expect) {
expect(true).that("Contradiction").equals(!true);
// Oops: .................................... [failed]
// ! Failure: Contradiction
// ! Expected: 1
// ! Received: 0
});
//
// testy will automatically record everything
// that went into std::cout and std::cerr. If
// the test doesn't fail, you won't be bothered.
//
testy::immediate("Logging", [](Expect expect) {
cout << "Good." << endl;
cerr << "Bad." << endl;
expect(true).that("Tautology").equals(true);
// Logging: ................................. [passed]
});
//
// Sometimes, you have a bunch of things to test,
// but you don't want 50 different tests. Thanks to
// Expectation.suppress, you can test everything in one.
//
testy::immediate("Suppression", [](Expect expect) {
expect.suppress = true;
cout << "Hello from the beginning." << endl;
expect(true).that("Tautology").equals(true);
expect(true).that("Contradiction").equals(false);
expect(static_cast<string>("typo")).that("Spelling").equals("ytpo"); // C++ likes to default to const char* ...
cerr << "Hello from the end." << endl;
// Suppression: ............................. [failed]
// ! Failure: Contradiction
// ! Expected: 1
// ! Received: 0
// ! Failure: Spelling
// ! Expected: typo
// ! Received: ytpo
// | Hello from the beginning.
// | Hello from the end.
});
return 0;
}
#pragma once
// ---------------------------------------------------------------------------------------------------------------------
// testy.h | A simple C++ testing library. | Copyright (C) 2018 Ethan Pini <epini@sfu.ca>
// ---------------------------------------------------------------------------------------------------------------------
// Includes:
#include <iostream>
#include <iomanip>
#include <string>
#include <exception>
#include <functional>
#include <memory>
#include <sstream>
#include <vector>
#include <type_traits>
#include <typeinfo>
#include <cstdint>
// ---------------------------------------------------------------------------------------------------------------------
namespace testy {
// ---------------------------------------------------------------------------------------------------------------------
// META:
/**
* SFINAE things...
*/
namespace META {
/**
* std::ostream& << T
*/
template<typename T>
class streamable {
template<typename C>
static auto check(int) -> decltype(std::declval<std::ostream &>() << std::declval<C>(), std::true_type());
template<typename>
static auto check(...) -> std::false_type;
public:
static const bool value = decltype(check<T>(0))
::value;
};
/**
* T == T
*/
template<typename T>
class comparable {
template<typename C>
static auto check(int) -> decltype(std::declval<C>() == std::declval<C>(), std::true_type());
template<typename>
static auto check(...) -> std::false_type;
public:
static const bool value = decltype(check<T>(0))
::value;
};
}
// ---------------------------------------------------------------------------------------------------------------------
// Util:
namespace Util {
/**
* Convert a non-streamable type to a string.
* This uses a stringstream to convert it.
* @tparam T The type.
* @return The string representation of the type.
*/
template<typename T>
std::string to_string(typename std::enable_if<!META::streamable<T>::value, T>::type);
/**
* Convert a non-streamable type to a string.
* This just returns the type name.
* @tparam T The type.
* @return The string representation of the type.
*/
template<typename T>
std::string to_string(typename std::enable_if<META::streamable<T>::value, T>::type);
};
// ---------------------------------------------------------------------------------------------------------------------
// Color:
/**
* Formatting codes for colored text output.
*/
class Color {
protected:
const std::string ansi;
public:
/**
* Creates a new Color object.
* @param ansi The ANSI escape sequence for this color.
*/
Color(const std::string &ansi);
/**
* Gets the color as an ANSI escape sequence.
* @return The ANSI escape sequence.
*/
const std::string &asANSI() const;
/**
* Casts to a string.
* @return The ANSI escape sequence.
*/
operator std::string() const;
// Constants:
static const Color RED;
static const Color GREEN;
static const Color YELLOW;
static const Color PURPLE;
static const Color PINK;
static const Color BLUE;
static const Color RESET;
};
const Color Color::RED = Color("\x1B[31m");
const Color Color::GREEN = Color("\x1B[32m");
const Color Color::YELLOW = Color("\x1B[33m");
const Color Color::PURPLE = Color("\x1B[34m");
const Color Color::PINK = Color("\x1B[35m");
const Color Color::BLUE = Color("\x1B[36m");
const Color Color::RESET = Color("\x1B[39m");
// ---------------------------------------------------------------------------------------------------------------------
// FailureReason:
/**
* An object containing information regarding why a test failed.
*/
class FailureReason {
public:
virtual ~FailureReason() = default;
/**
* Gets a unique identifier representing to object kind.
* This is used to identify the class before casting.
* @return A unique identifier.
*/
virtual std::string kind() const = 0;
/**
* Converts the failure reason to a string.
* @param formatted Whether or not to use console-friendly formatting.
* @return A string representing the failure reason.
*/
virtual std::string toString(bool formatted = true) const = 0;
/**
* Casts to a string.
* @return The string.
*/
operator std::string() const;
};
// ---------------------------------------------------------------------------------------------------------------------
// Failure:
/**
* A runtime error for signaling the failure of a test.
*/
class Failure : public std::runtime_error {
protected:
const std::shared_ptr <FailureReason> failureReason;
public:
/**
* Creates a new test failure error.
* @param what The failure message.
* @param reasons The failure reason.
*/
Failure(const std::string &what, const std::shared_ptr <FailureReason> reasons);
virtual ~Failure() = default;
/**
* Gets the reason why the test failed.
* @return The failure reason.
*/
const std::shared_ptr <FailureReason> reason() const;
};
// ---------------------------------------------------------------------------------------------------------------------
// FailureListener:
/**
* A class which listens for failures.
*/
class FailureListener {
public:
virtual ~FailureListener() = default;
/**
* Callback. This is called when a test failure occurs.
* @param failure The failure.
*/
virtual void fail(const Failure &failure) = 0;
};
// ---------------------------------------------------------------------------------------------------------------------
// UncaughtExceptionReason:
/**
* A failure reason that represents an uncaught exception.
*/
class UncaughtExceptionReason : public FailureReason {
protected:
const std::string whatValue;
public:
/**
* Creates a new UncaughtExceptionReason.
* @param what The exception what() string.
*/
UncaughtExceptionReason(const std::string &what);
~UncaughtExceptionReason() = default;
/**
* Gets the exception what() string.
* @return The what string.
*/
const std::string &what() const;
/**
* @inheritDoc
*/
std::string kind() const override;
/**
* @inheritDoc
*/
std::string toString(bool formatted = true) const override;
};
// ---------------------------------------------------------------------------------------------------------------------
// ExpectationReason:
/**
* A failure reason that represents a failed expectation.
*/
class ExpectationReason : public FailureReason {
protected:
const std::string valueExpected;
const std::string valueReceived;
const bool conditionNegated;
public:
/**
* Creates a new ExpectationReason.
* @param expected The expected value.
* @param received The received value.
* @param negated Whether the expectation was negated or not.
*/
ExpectationReason(std::string expected, std::string received, bool negated);
~ExpectationReason() = default;
/**
* Gets the expected value.
* @return The expected value as a string.
*/
const std::string &expected() const;
/**
* Gets the received value.
* @return The received value as a string.
*/
const std::string &received() const;
/**
* @inheritDoc
*/
std::string kind() const override;
/**
* @inheritDoc
*/
std::string toString(bool formatted = true) const override;
};
// ---------------------------------------------------------------------------------------------------------------------
// Expectation:
/**
* An abstract class for asserting expectations.
*/
template<class S>
class Expectation {
protected:
bool negate;
std::string name;
FailureListener* listener;
/**
* Cleans up leftovers from the previous asserable expectation.
* Currently, this just resets the negate flag and name.
*/
virtual void _reset();
/**
* Sends a failure to the listener, or throws if it's null.
* @param failure The failure.
*/
virtual void _throw(const Failure &failure);
/**
* Expect a specific string is sent to a stream.
* @param func The function to run.
* @param expected The expected value sent to cout.
*/
virtual void _streamEquals(std::ostream*, std::function<void()> func, std::string expected);
public:
/**
* Creates a new abstract expectation.
* @param listener The failure listener.
*/
Expectation(FailureListener* listener = nullptr);
virtual ~Expectation() = default;
/**
* Sets the expectation name.
* @param name The expectation name.
* @return The expectation (for chaining).
*/
S &that(std::string name);
/**
* Negate the expectation.
* This negation is reset after every call.
* @return The expectation (for chaining).
*/
S &never();
};
// ---------------------------------------------------------------------------------------------------------------------
// ValueExpectation:
/**
* A class for asserting expectations on values.
* @tparam T The type of value.
*/
template<typename T>
class ValueExpectation : public Expectation<ValueExpectation<T>> {
protected:
const T value;
public:
/**
* Creates a new expectation for a value.
* @param value The value to have expectations for.
* @param listener The failure listener.
*/
ValueExpectation(const T &value, FailureListener* listener);
/**
* Expects that the values are equal.
* @param expected The expected value.
* @return The expectation (for chaining).
* @throws Failure
*/
ValueExpectation<T> &equals(T expected);
};
// ---------------------------------------------------------------------------------------------------------------------
// FunctionExpectation:
/**
* A class for asserting expectations on functions.
*/
class FunctionExpectation : public Expectation<FunctionExpectation> {
protected:
const std::function<void()> func;
public:
/**
* Creates a new expectation for a function.
* @param func The function to have expectations for.
* @param listener The failure listener.
*/
FunctionExpectation(std::function<void()> func, FailureListener* listener)
: Expectation<FunctionExpectation>(listener),
func(func) {}
/**
* Expects cout to be equal to a provided string.
* @param expected The expected value of cout.
*/
FunctionExpectation &couts(const std::string &expected);
/**
* Expects cerr to be equal to a provided string.
* @param expected The expected value of cerr.
*/
FunctionExpectation &cerrs(const std::string &expected);
/**
* Expects an exception to be thrown.
* @tparam E The expected exception.
*/
template<class E>
FunctionExpectation &throws();
};
// ---------------------------------------------------------------------------------------------------------------------
// Expect:
/**
* A class for creating expectations.
*/
class Expect {
protected:
FailureListener* listener;
public:
/**
* Creates a new Expect object.
*/
Expect();
/**
* Creates a new Expect object with a failure listener.
* @param listener The failure listener.
*/
Expect(FailureListener* listener);
/**
* Whether or not failed expectations will throw an error, or silently let the test continue.
*/
bool suppress = true;
/**
* Creates an expectation with a value.
* @tparam T The type to expect on.
* @param value The value which the expectation applies to.
*/
template<typename T>
ValueExpectation<T> operator()(const T &value);
/**
* Creates an expectation with a function.
* @tparam T The function template.
* @param func The value which the expectation applies to.
*/
FunctionExpectation function(const std::function<void()> &func);
};
// ---------------------------------------------------------------------------------------------------------------------
// Test:
/**
* A runnable test.
*/
class Test : public FailureListener {
protected:
const std::string testName;
const std::function<void(Expect &)> runnable;
std::vector <Failure> testFailures;
std::string testStdout;
std::string testStderr;
bool testPassed;
public:
/**
* Creates a new test.
* @param name The test name.
* @param func The test function.
*/
Test(std::string name, std::function<void(Expect &)> func);
/**
* Runs the test.
* @return True if the test passed, false otherwise.
*/
virtual bool run();
/**
* @inheritDoc
*/
virtual void fail(const Failure &failure) override;
/**
* Gets the cout result of the test.
* @return The cout result.
*/
virtual std::stringstream stdout() const;
/**
* Gets the cerr result of the test.
* @return The cerr result.
*/
virtual std::stringstream stderr() const;
/**
* Gets whether or not the test passed.
* @return Whether or not the test passed.
*/
bool passed() const;
/**
* Gets the reasons why the test failed.
* @return The test failures.
*/
const std::vector <Failure> &failures() const;
/**
* Gets the name of the test.
* @return
*/
const std::string &name() const;
};
// ---------------------------------------------------------------------------------------------------------------------
// Testy:
/**
* The main class of Testy.
*
* This could be considered a test suite.
*/
class Testy {
protected:
std::vector <Test> testList;
size_t testNameWidth;
/**
* Prints a stream to another stream.
* @param in The stream to read from.
* @param out The stream to write to.
* @param prefix The prefix to write before each line.
*/
virtual void _printStream(std::istream &in, std::ostream &out, const std::string &prefix) const;
public:
Testy();
virtual ~Testy() = default;
/**
* Gets the list of tests.
* @return The vector containing the tests.
*/
std::vector <Test> tests();
/**
* Adds a test to the suite.
* @param name The name of the test.
* @param test The test function.
*/
virtual void test(std::string name, std::function<void(Expect)> test);
/**
* Runs all the tests.
*/
virtual void run();
/**
* Reports on all the tests.
* @param verbose Whether or not to print details for all tests, or just failed ones.
* @param stream The stream to report to.
*/
virtual void report(bool verbose = false, std::ostream &stream = std::cout);
};
// ---------------------------------------------------------------------------------------------------------------------
// immediate
/**
* Immediately run a test.
* @param name The name of the test.
* @param test The test function.
*/
void immediate(std::string name, std::function<void(Expect)> test);
// ---------------------------------------------------------------------------------------------------------------------
}
// =====================================================================================================================
// =====================================================================================================================
// =====================================================================================================================
// ---------------------------------------------------------------------------------------------------------------------
// testy.cpp | A simple C++ testing library. | Copyright (C) 2018 Ethan Pini <epini@sfu.ca>
// ---------------------------------------------------------------------------------------------------------------------
namespace testy {
// ---------------------------------------------------------------------------------------------------------------------
// Util:
template<typename T>
std::string Util::to_string(typename std::enable_if<!META::streamable<T>::value, T>::type) {
return (std::string) "<" + typeid(T).name() + ">";
}
template<typename T>
std::string Util::to_string(typename std::enable_if<META::streamable<T>::value, T>::type value) {
std::stringstream convert;
convert << value;
return convert.str();
}
// ---------------------------------------------------------------------------------------------------------------------
// Color:
Color::Color(const std::string &ansi) : ansi(ansi) {
}
Color::operator std::string() const {
return this->ansi;
}
const std::string &Color::asANSI() const {
return this->ansi;
}
std::ostream &operator<<(std::ostream &stream, const Color &color) {
stream << static_cast<std::string>(color);
return stream;
}
// ---------------------------------------------------------------------------------------------------------------------
// Expectation:
template<typename T>
Expectation<T>::Expectation(FailureListener* listener)
: listener(listener) {
this->name = "[Anonymous Expectation]";
this->_reset();
}
template<typename T>
void Expectation<T>::_throw(const Failure &failure) {
if (this->listener == nullptr) {
throw failure;
} else {
this->listener->fail(failure);
}
}
template<typename T>
void Expectation<T>::_reset() {
this->negate = false;
}
/**
* Expect a specific string is sent to a stream.
* @param func The function to run.
* @param expected The expected value sent to cout.
*/
template<typename T>
void Expectation<T>::_streamEquals(std::ostream* stream, std::function<void()> func, std::string expected) {
// Cache settings and reset.
std::string name = this->name;
bool negate = this->negate;
this->_reset();
// Redirect stream.
std::stringstream buffer;
std::streambuf* backup = stream->rdbuf();
stream->rdbuf(buffer.rdbuf());
// Run lambda.
std::exception_ptr exception = nullptr;
try {
func();
} catch (...) {
exception = std::current_exception();
}
// Restore stream.
stream->rdbuf(backup);
// Handle exception.
if (exception != nullptr) {
rethrow_exception(exception);
}
// Compare.
if ((buffer.str() == expected) == negate) {
this->_throw(Failure(name, std::make_shared<ExpectationReason>(expected, buffer.str(), negate)));
}
}
template<typename T>
T &Expectation<T>::that(std::string name) {
this->name = name;
return *static_cast<T*>(this);
}
template<typename T>
T &Expectation<T>::never() {
this->negate = true;
return *static_cast<T*>(this);
}
// ---------------------------------------------------------------------------------------------------------------------
// ValueExpectation:
template<typename T>
ValueExpectation<T>::ValueExpectation(const T &value, FailureListener* listener)
: Expectation<ValueExpectation<T>>(listener),
value(value) {
this->_reset();
}
template<typename T>
ValueExpectation<T> &ValueExpectation<T>::equals(T value) {
static_assert(META::comparable<T>::value == std::true_type::value,
"testy.h: Cannot run assertion equals(T); Type T must have an overloaded == operator.");
// Cache settings and reset.
std::string name = this->name;
bool negate = this->negate;
this->_reset();
// Compare values.
if ((this->value == value) == negate) {
this->_throw(Failure(name, std::make_shared<ExpectationReason>(
Util::to_string<T>(this->value),
Util::to_string<T>(value),
negate
)));
}
return *this;
}
// ---------------------------------------------------------------------------------------------------------------------
// Expectation<std::function<T>>:
FunctionExpectation &FunctionExpectation::couts(const std::string &expected) {
this->_streamEquals(&std::cout, this->func, expected);
return *this;
}
FunctionExpectation &FunctionExpectation::cerrs(const std::string &expected) {
this->_streamEquals(&std::cerr, this->func, expected);
return *this;
}
template<typename E>
FunctionExpectation &FunctionExpectation::throws() {
// Cache settings and reset.
std::string name = this->name;
bool negate = this->negate;
this->_reset();
// Run the function, returning if the expected action occurred.
try {
func();
if (negate) {
return *this;
}
} catch (const E &) {
if (!negate) {
return *this;
}
}
// The expected action did not occur.
// Generate a failure and throw it.
this->_throw(Failure(
name,
std::make_shared<ExpectationReason>((std::string) "throw " + typeid(E).name(), "<nothing>", negate)
));
// Return this.
return *this;
}
// ---------------------------------------------------------------------------------------------------------------------
// Expect:
Expect::Expect() {}
Expect::Expect(FailureListener* listener)
: listener(listener) {}
template<typename T>
ValueExpectation<T> Expect::operator()(const T &value) {
return ValueExpectation<T>(value, this->suppress ? this->listener : nullptr);
}
FunctionExpectation Expect::function(const std::function<void()> &func) {
return FunctionExpectation(func, this->suppress ? this->listener : nullptr);
}
// ---------------------------------------------------------------------------------------------------------------------
// Failure:
Failure::Failure(const std::string &what, std::shared_ptr <FailureReason> reason)
: std::runtime_error(what),
failureReason(reason) {
}
const std::shared_ptr <FailureReason> Failure::reason() const {
return this->failureReason;
}
// ---------------------------------------------------------------------------------------------------------------------
// FailureReason:
FailureReason::operator std::string() const {
return this->toString();
}
// ---------------------------------------------------------------------------------------------------------------------
// UncaughtExceptionReason:
UncaughtExceptionReason::UncaughtExceptionReason(const std::string &what)
: FailureReason(),
whatValue(what) {}
const std::string &UncaughtExceptionReason::what() const {
return this->whatValue;
}
std::string UncaughtExceptionReason::kind() const {
return typeid(UncaughtExceptionReason).name();
}
std::string UncaughtExceptionReason::toString(bool formatted) const {
std::stringstream buffer;
const std::string &RED = formatted ? Color::RED.asANSI() : "";
const std::string &RESET = formatted ? Color::RESET.asANSI() : "";
buffer << RED << "Exception: " << this->what() << RESET << std::endl;
return buffer.str();
}
// ---------------------------------------------------------------------------------------------------------------------
// ExpectationReason:
ExpectationReason::ExpectationReason(std::string expected, std::string received, bool negated)
: FailureReason(),
valueExpected(expected),
valueReceived(received),
conditionNegated(negated) {
}
const std::string &ExpectationReason::expected() const {
return this->valueExpected;
}
const std::string &ExpectationReason::received() const {
return this->valueReceived;
}
std::string ExpectationReason::kind() const {
return typeid(ExpectationReason).name();
}
std::string ExpectationReason::toString(bool formatted) const {
std::stringstream buffer;
const std::string &YELLOW = formatted ? Color::YELLOW.asANSI() : "";
const std::string &GREEN = formatted ? Color::GREEN.asANSI() : "";
const std::string &RED = formatted ? Color::RED.asANSI() : "";
const std::string &RESET = formatted ? Color::RESET.asANSI() : "";
buffer << YELLOW << (this->conditionNegated ? "Expected Not: " : "Expected: ") << GREEN << this->expected() << std::endl
<< YELLOW << (this->conditionNegated ? "Received: " : "Received: ") << RED << this->received() << RESET << std::endl;
return buffer.str();
}
// ---------------------------------------------------------------------------------------------------------------------
// Test:
Test::Test(std::string name, std::function<void(Expect &)> func)
: testName(name),
runnable(func) {}
std::stringstream Test::stdout() const {
return std::stringstream(this->testStdout);
}
std::stringstream Test::stderr() const {
return std::stringstream(this->testStderr);
}
bool Test::passed() const {
return this->testPassed;
}
const std::vector <Failure> &Test::failures() const {
return this->testFailures;
}
const std::string &Test::name() const {
return this->testName;
}
bool Test::run() {
Expect expect = Expect(this);
// Replace cout/cerr.
std::stringstream captureStdout;
std::stringstream captureStderr;
std::streambuf* coutBackup = std::cout.rdbuf();
std::streambuf* cerrBackup = std::cerr.rdbuf();
std::cout.rdbuf(captureStdout.rdbuf());
std::cerr.rdbuf(captureStderr.rdbuf());
// Run the test.
this->testPassed = true;
this->testFailures.clear();
try {
this->runnable(expect);
if (this->testFailures.size() > 0) {
this->testPassed = false;
}
} catch (const Failure &failure) {
this->testPassed = false;
this->testFailures.push_back(failure);
} catch (const std::exception &exception) {
this->testPassed = false;
this->testFailures.push_back(Failure("Uncaught exception.", std::make_shared<UncaughtExceptionReason>(exception.what())));
} catch (...) {
this->testPassed = false;
this->testFailures.push_back(Failure("Uncaught exception?", std::make_shared<UncaughtExceptionReason>("???")));
}
// Restore cout/cerr and copy captured.
std::cout.rdbuf(coutBackup);
std::cerr.rdbuf(cerrBackup);
this->testStdout = captureStdout.str();
this->testStderr = captureStderr.str();
// Return.
return this->testPassed;
}
void Test::fail(const Failure &failure) {
this->testFailures.push_back(failure);
}
// ---------------------------------------------------------------------------------------------------------------------
// Testy:
Testy::Testy()
: testNameWidth(40) {
}
void Testy::_printStream(std::istream &in, std::ostream &out, const std::string &prefix) const {
std::stringstream buffer;
std::string line;
while (std::getline(in, line)) {
if (line.length() == 0) {
buffer << prefix << std::endl;
} else {
out << buffer.str()
<< prefix << line << std::endl;
buffer.clear();
}
}
out << Color::RESET;
}
std::vector <Test> Testy::tests() {
return this->testList;
}
void Testy::test(std::string name, std::function<void(Expect)> test) {
if (name.length() > this->testNameWidth) {
this->testNameWidth = name.length();
}
this->testList.push_back(Test(name, test));
}
void Testy::run() {
for (auto &test: this->testList) {
test.run();
}
}
void Testy::report(bool verbose, std::ostream &stream) {
size_t passed = 0;
size_t total = 0;
for (auto &test: this->testList) {
total++;
passed += test.passed() ? 1 : 0;
// Display test name.
const std::string &name = test.name();
size_t nameLength = name.length();
std::string namePadding = std::string(this->testNameWidth - nameLength, '.');
stream << Color::BLUE << name << Color::RESET << ": " << namePadding << " ";
// Display test status.
if (test.passed()) {
stream << Color::GREEN << "[passed]" << Color::RESET << std::endl;
} else {
stream << Color::RED << "[failed]" << Color::RESET << std::endl;
}
// Skip to the next one if passed and not verbose.
if (test.passed() && !verbose) {
continue;
}
// Display test failures.
std::vector <Failure> failures = test.failures();
for (auto &failure: failures) {
stream << Color::RED << "! Failure: " << failure.what() << Color::RESET << std::endl;
std::stringstream reason;
reason << failure.reason()->toString();
this->_printStream(reason, stream, Color::RED.asANSI() + "! " + Color::RESET.asANSI());
}
// Display cout/cerr.
std::stringstream temp;
this->_printStream((temp = test.stdout()), stream, Color::YELLOW.asANSI() + "| " + Color::RESET.asANSI());
this->_printStream((temp = test.stderr()), stream, Color::RED.asANSI() + "| " + Color::RESET.asANSI());
}
// // Total.
// stream << std::endl << std::endl
// << Color::GREEN << passed << " tests passed." << Color::RESET << std::endl;
//
// if (passed == total) {
// stream << Color::GREEN << "ALL TESTS HAVE PASSED!" << Color::RESET << std::endl;
// } else {
// stream << Color::RED << (total - passed) << " tests failed." << Color::RESET << std::endl;
// }
}
// ---------------------------------------------------------------------------------------------------------------------
// immediate:
void immediate(std::string name, std::function<void(Expect)> test) {
Testy suite;
suite.test(name, test);
suite.run();
suite.report(false);
}
// ---------------------------------------------------------------------------------------------------------------------
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment