Finally have something useful.
This commit is contained in:
10
include/JTest/ConfigureFunction.h
Normal file
10
include/JTest/ConfigureFunction.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef JTEST__CONFIGUREFUNCTION_H__
|
||||
#define JTEST__CONFIGUREFUNCTION_H__
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
namespace JTest {
|
||||
typedef std::function<void()> configure_fn;
|
||||
typedef std::optional<configure_fn> maybe_configure_fn;
|
||||
maybe_configure_fn combine(maybe_configure_fn first, maybe_configure_fn second);
|
||||
}
|
||||
#endif
|
||||
29
include/JTest/DescribeOptions.h
Normal file
29
include/JTest/DescribeOptions.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef JTEST__DESCRIBEOPTIONS_H__
|
||||
#define JTEST__DESCRIBEOPTIONS_H__
|
||||
namespace JTest {
|
||||
class DescribeOptions;
|
||||
}
|
||||
#include <optional>
|
||||
#include <JTest/ConfigureFunction.h>
|
||||
|
||||
//TODO: Add operator<< and maybe print(ostream&)
|
||||
namespace JTest {
|
||||
class DescribeOptions {
|
||||
public:
|
||||
DescribeOptions();
|
||||
maybe_configure_fn getAfterAll() const;
|
||||
maybe_configure_fn getAfterEach() const;
|
||||
maybe_configure_fn getBeforeAll() const;
|
||||
maybe_configure_fn getBeforeEach() const;
|
||||
DescribeOptions& afterAll(maybe_configure_fn fn);
|
||||
DescribeOptions& afterEach(maybe_configure_fn fn);
|
||||
DescribeOptions& beforeAll(maybe_configure_fn fn);
|
||||
DescribeOptions& beforeEach(maybe_configure_fn fn);
|
||||
private:
|
||||
maybe_configure_fn afterAll_;
|
||||
maybe_configure_fn afterEach_;
|
||||
maybe_configure_fn beforeAll_;
|
||||
maybe_configure_fn beforeEach_;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
259
include/JTest/Expectable.h
Normal file
259
include/JTest/Expectable.h
Normal file
@@ -0,0 +1,259 @@
|
||||
#ifndef JTEST__EXPECTABLE_H__
|
||||
#define JTEST__EXPECTABLE_H__
|
||||
namespace JTest {
|
||||
template<typename T>
|
||||
class Expectable;
|
||||
}
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <JTest/FailedExpectation.h>
|
||||
#include <type_traits>
|
||||
|
||||
namespace JTest {
|
||||
template<typename T>
|
||||
class Expectable {
|
||||
public:
|
||||
Expectable(const T& actual)
|
||||
: actual_(actual)
|
||||
, is_inverted_(false) {}
|
||||
|
||||
Expectable(const T& actual, bool is_inverted)
|
||||
: actual_(actual)
|
||||
, is_inverted_(is_inverted) {}
|
||||
|
||||
virtual ~Expectable() {}
|
||||
|
||||
const T& actual() {
|
||||
return actual_;
|
||||
}
|
||||
|
||||
bool isInverted() {
|
||||
return is_inverted_;
|
||||
}
|
||||
|
||||
virtual Expectable<T> nevermore() {
|
||||
return Expectable(actual_, !is_inverted_);
|
||||
}
|
||||
|
||||
void toBe(std::function<std::optional<std::string>(const T&)> matcher) {
|
||||
auto reason = matcher(actual_);
|
||||
if (is_false(reason.has_value())) {
|
||||
throw FailedExpectation(make_failure_message(*reason));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2 = T>
|
||||
typename std::enable_if<std::is_convertible<T2, bool>::value, void>::type
|
||||
toBeFalse() {
|
||||
if (is_true(actual_)) {
|
||||
throw FailedExpectation(make_failure_message("to be false"));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2 = T>
|
||||
typename std::enable_if<std::is_pointer<T2>::value, void>::type
|
||||
toBeNull() {
|
||||
if (is_false(actual_ == nullptr)) {
|
||||
throw FailedExpectation(make_failure_message("to be null"));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2 = T>
|
||||
typename std::enable_if<std::is_convertible<T2, bool>::value, bool>::type
|
||||
toBeTrue() {
|
||||
if (is_false(actual_)) {
|
||||
throw FailedExpectation(make_failure_message("to be true"));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2 = T>
|
||||
typename std::enable_if<decltype(std::declval<std::equal_to <>>()(std::declval<T2>(), std::declval<T2>()), void(), std::true_type())::type::value, void>::type
|
||||
toEqual(const T& value) {
|
||||
if (is_false(actual_ == value)) {
|
||||
throw FailedExpectation(make_failure_message("to equal", value));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2 = T>
|
||||
typename std::enable_if<std::is_invocable<T2>::value, bool>::type
|
||||
toThrow() {
|
||||
bool has_failed;
|
||||
std::string reason;
|
||||
try {
|
||||
// TODO: Do some magic here to allow for args and member functions.
|
||||
std::invoke(actual_);
|
||||
has_failed = is_false();
|
||||
if (has_failed) {
|
||||
reason = "to throw, but did not";
|
||||
}
|
||||
} catch(...) {
|
||||
has_failed = is_true();
|
||||
if (has_failed) {
|
||||
reason = "to throw, but threw something";
|
||||
}
|
||||
}
|
||||
if (has_failed) {
|
||||
throw FailedExpectation(make_failure_message(reason));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2=T>
|
||||
typename std::enable_if<std::is_invocable<T2>::value, bool>::type
|
||||
toThrow(const std::exception& expected) {
|
||||
bool has_failed;
|
||||
std::string reason = "";
|
||||
try {
|
||||
// TODO: Do some magic here to allow for args and member functions.
|
||||
std::invoke(actual_);
|
||||
has_failed = is_false();
|
||||
if (has_failed) {
|
||||
reason = ", but did not throw";
|
||||
}
|
||||
} catch(const std::exception& actual) {
|
||||
has_failed = is_true(strcmp(expected.what(), actual.what()) == 0);
|
||||
if (has_failed) {
|
||||
throw FailedExpectation(make_failure_message("to throw", expected.what(), actual.what()));
|
||||
reason = string(is_inverted_ ? "" : ", but threw ") + actual.what();
|
||||
}
|
||||
} catch(...) {
|
||||
has_failed = is_false();
|
||||
reason = "but threw something else";
|
||||
reason = is_inverted_ ? "" : ", but threw something else";
|
||||
}
|
||||
if (has_failed) {
|
||||
throw FailedExpectation(make_failure_message("to throw " + (expected.what() + reason)));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2=T>
|
||||
typename std::enable_if<std::is_invocable<T2>::value, bool>::type
|
||||
toThrow(std::function<std::optional<std::string>(const std::exception&)> matcher) {
|
||||
bool has_failed;
|
||||
std::optional<std::string> reason = std::nullopt;
|
||||
try {
|
||||
// TODO: Do some magic here to allow for args and member functions.
|
||||
std::invoke(actual_);
|
||||
has_failed = is_false();
|
||||
if (has_failed) {
|
||||
reason = "did not throw";
|
||||
}
|
||||
} catch(const std::exception& actual) {
|
||||
reason = matcher(actual);
|
||||
has_failed = is_true(reason.has_value());
|
||||
} catch(...) {
|
||||
has_failed = is_false();
|
||||
if (has_failed) {
|
||||
reason = "threw something else";
|
||||
}
|
||||
}
|
||||
if (has_failed) {
|
||||
if (reason.has_value()) {
|
||||
throw FailedExpectation(make_failure_message(is_inverted_ ? "to throw, and " : "to throw, but " + *reason));
|
||||
}
|
||||
// This should never be hit.
|
||||
throw FailedExpectation(make_failure_message("to throw"));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T2=T>
|
||||
typename std::enable_if<std::is_invocable<T2>::value, bool>::type
|
||||
toThrow(const std::string& what) {
|
||||
bool has_failed;
|
||||
std::string reason;
|
||||
try {
|
||||
// TODO: Do some magic here to allow for args and member functions.
|
||||
std::invoke(actual_);
|
||||
has_failed = is_false();
|
||||
if (has_failed) {
|
||||
reason = "to throw " + what + ", but did not throw anything";
|
||||
}
|
||||
} catch(const std::exception& actual) {
|
||||
has_failed = is_true(what == actual.what());
|
||||
if (has_failed) {
|
||||
reason = "to throw " + what + ", but actually threw " + actual.what();
|
||||
}
|
||||
} catch(...) {
|
||||
has_failed = is_false();
|
||||
if (has_failed) {
|
||||
reason = "to throw " + what + ", but threw something that is not an exception";
|
||||
}
|
||||
}
|
||||
if (has_failed) {
|
||||
throw FailedExpectation(make_failure_message(reason));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const T& actual_;
|
||||
const bool is_inverted_;
|
||||
bool is_true(bool condition = true) {
|
||||
return condition != is_inverted_;
|
||||
}
|
||||
bool is_false(bool condition = true) {
|
||||
return condition == is_inverted_;
|
||||
}
|
||||
|
||||
// std::string make_failure_message(const T& expected, const T& actual) {
|
||||
// ostringstream os;
|
||||
// os << "expected: " << value << ", actual: " << actual_;
|
||||
// return os.str();
|
||||
// }
|
||||
// Failed Expectation:
|
||||
// to be equal with expected = 12, and actual = 0
|
||||
// Failed Expectation:
|
||||
// not to be equal with expected = 12, and actual = 12
|
||||
|
||||
std::string make_failure_message(const std::string reason) {
|
||||
std::ostringstream os;
|
||||
os << (is_inverted_ ? "not " : "") << reason << " with actual = " << actual_;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string make_failure_message(const std::string reason, const T& expected) {
|
||||
std::ostringstream os;
|
||||
os << (is_inverted_ ? "not " : "") << reason << " with expected = " << expected << ", and actual = " << actual_;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string make_failure_message(const std::string reason, std::string expected, std::string actual) {
|
||||
std::ostringstream os;
|
||||
os << (is_inverted_ ? "not " : "") << reason
|
||||
<< " with expected message = \"" << expected
|
||||
<< "\", and actual message = \"" << actual_ << "\"";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
// TODO: Fix the make_failure_message methods so they only exist if operator<< is defined for (ostream&, T)
|
||||
// Maybe have it generate a shorter message.
|
||||
// Maybe also check for string + T and use that if operator<< does not exist.
|
||||
// If neither of those exist this should at least say "expected not equal to actual"
|
||||
// Also this should take is_inverted_ into account.
|
||||
};
|
||||
|
||||
// Other possible names since Expectable<T> not() is not an option
|
||||
// Wanted, but can't have: expect(2+2).not().toBe(5);
|
||||
// expect(2+2).absolutelyNot().toBe(5);
|
||||
// expect(2+2).definitelyNot().toBe(5);
|
||||
// expect(2+2).isNot().toBe(5);
|
||||
// expect(2+2).isNotGoing().toBe(5);
|
||||
// expect(2+2).nay().toBe(5);
|
||||
// expect(2+2).neer().toBe(5);
|
||||
// expect(2+2).neither().toBe(5);
|
||||
// expect(2+2).never().toBe(5);
|
||||
// expect(2+2).nevermore().toBe(5);
|
||||
// expect(2+2).nix().toBe(5);
|
||||
// expect(2+2).no().toBe(5);
|
||||
// expect(2+2).notAtAll().toBe(5);
|
||||
// expect(2+2).notByAnyMeans().toBe(5);
|
||||
// expect(2+2).surelyNot().toBe(5);
|
||||
|
||||
// This is for T actual(){...} since Expectable<void()> has no default constructor.
|
||||
// Expectable<void()> won't compile no default constructor exists for class "Expectable<void ()>"
|
||||
// template<typename T2 = T>
|
||||
// typename std::enable_if<std::is_default_constructible<T2>::value, T>::type
|
||||
}
|
||||
|
||||
#endif
|
||||
16
include/JTest/FailedExpectation.h
Normal file
16
include/JTest/FailedExpectation.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef JTEST__FAILEDEXPECTATION_H__
|
||||
#define JTEST__FAILEDEXPECTATION_H__
|
||||
namespace JTest {
|
||||
class FailedExpectation;
|
||||
}
|
||||
#include <string>
|
||||
#include <JTest/TestFailedException.h>
|
||||
namespace JTest {
|
||||
class FailedExpectation: public TestFailedException {
|
||||
public:
|
||||
FailedExpectation(const std::string& label);
|
||||
FailedExpectation(const std::string& label, const std::string& path, const std::string& reason);
|
||||
virtual ~FailedExpectation();
|
||||
};
|
||||
}
|
||||
#endif
|
||||
39
include/JTest/JTest.h
Normal file
39
include/JTest/JTest.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef JTEST_H__
|
||||
#define JTEST_H__
|
||||
namespace JTest {
|
||||
}
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <JTest/TestResults.h>
|
||||
#include <JTest/TestBundle.h>
|
||||
#include <JTest/Test.h>
|
||||
#include <JTest/Expectable.h>
|
||||
#include <JTest/TestOptions.h>
|
||||
#include <JTest/DescribeOptions.h>
|
||||
#include <JTest/TestFunction.h>
|
||||
#include <JTest/TestBundle.h>
|
||||
|
||||
namespace JTest {
|
||||
// Jasmine methods
|
||||
|
||||
// Executes the tests in tests. Possibly in parallel. Will block until all async tests have completed.
|
||||
TestResults execute(TestBundle tests, const std::string& path="");
|
||||
TestResults execute(Test test, const std::string& bundle_label);
|
||||
|
||||
//
|
||||
TestBundle describe(const std::string& label, const make_testbundle_fn& make_tests, std::optional<DescribeOptions> options = std::nullopt);
|
||||
TestBundle xdescribe(const std::string& label, const make_testbundle_fn& make_tests, std::optional<DescribeOptions> options = std::nullopt);
|
||||
|
||||
// TODO: Make this return a Test instead.
|
||||
TestBundle it(const std::string& label, const test_fn& test_method, std::optional<TestOptions> options = std::nullopt);
|
||||
TestBundle xit(const std::string& label, const test_fn& test_method, std::optional<TestOptions> options = std::nullopt);
|
||||
|
||||
void fail(const std::string& reason);
|
||||
void pending(const std::string& reason);
|
||||
|
||||
template<typename T>
|
||||
Expectable<T> expect(const T& actual) {
|
||||
return Expectable(actual);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
17
include/JTest/Test.h
Normal file
17
include/JTest/Test.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef JTEST__TEST_H__
|
||||
#define JTEST__TEST_H__
|
||||
namespace JTest {
|
||||
class Test;
|
||||
}
|
||||
#include <string>
|
||||
#include <JTest/TestFunction.h>
|
||||
namespace JTest {
|
||||
class Test {
|
||||
public:
|
||||
std::string label_;
|
||||
test_fn test_method_;
|
||||
bool disabled_;
|
||||
private:
|
||||
};
|
||||
}
|
||||
#endif
|
||||
33
include/JTest/TestBundle.h
Normal file
33
include/JTest/TestBundle.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef JEST__TESTBUNDLE_H__
|
||||
#define JEST__TESTBUNDLE_H__
|
||||
#include <functional>
|
||||
namespace JTest {
|
||||
class TestBundle;
|
||||
typedef std::function<TestBundle()> make_testbundle_fn;
|
||||
}
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <JTest/ConfigureFunction.h>
|
||||
#include <JTest/DescribeOptions.h>
|
||||
#include <JTest/TestBundle.h>
|
||||
#include <JTest/Test.h>
|
||||
namespace JTest {
|
||||
class TestBundle {
|
||||
public:
|
||||
TestBundle(const std::vector<TestBundle>& bundles, const DescribeOptions& options);
|
||||
TestBundle(const std::string& label, const std::vector<Test>& tests);
|
||||
std::string fully_qualified_label() const;
|
||||
std::string label_;
|
||||
std::string fully_qualified_path_;
|
||||
std::vector<Test> tests_;
|
||||
std::vector<TestBundle> children_;
|
||||
maybe_configure_fn afterAll_;
|
||||
maybe_configure_fn afterEach_;
|
||||
maybe_configure_fn beforeAll_;
|
||||
maybe_configure_fn beforeEach_;
|
||||
bool disabled_;
|
||||
private:
|
||||
};
|
||||
}
|
||||
#endif
|
||||
28
include/JTest/TestException.h
Normal file
28
include/JTest/TestException.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef JTEST__TESTEXCEPTION_H__
|
||||
#define JTEST__TESTEXCEPTION_H__
|
||||
namespace JTest {
|
||||
class TestException;
|
||||
}
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <JTest/TestStatus.h>
|
||||
namespace JTest {
|
||||
class TestException: public std::runtime_error {
|
||||
public:
|
||||
TestException(const std::string& label, const std::string& path);
|
||||
TestException(const std::string& label, const std::string& path, const std::string& what);
|
||||
TestException(const std::string& function, const std::string& file, int line, const std::string& reason);
|
||||
virtual ~TestException();
|
||||
virtual std::string test_label() const;
|
||||
virtual std::string fully_qualified_test_label() const;
|
||||
virtual TestStatus status() const;
|
||||
private:
|
||||
const std::string test_label_;
|
||||
const std::string fully_qualified_test_label_;
|
||||
const std::string function_;
|
||||
const std::string file_;
|
||||
const int line_;
|
||||
const std::string reason_;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
20
include/JTest/TestFailedException.h
Normal file
20
include/JTest/TestFailedException.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef JTEST__TESTFAILEDEXCEPTION_H__
|
||||
#define JTEST__TESTFAILEDEXCEPTION_H__
|
||||
namespace JTest{
|
||||
class TestFailedException;
|
||||
}
|
||||
#include <string>
|
||||
#include <JTest/TestStatus.h>
|
||||
#include <JTest/TestException.h>
|
||||
namespace JTest{
|
||||
class TestFailedException: public TestException {
|
||||
public:
|
||||
TestFailedException(std::string label, std::string path, std::string reason);
|
||||
virtual ~TestFailedException();
|
||||
virtual TestStatus status() const;
|
||||
virtual std::string reason() const;
|
||||
private:
|
||||
std::string reason_;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
7
include/JTest/TestFunction.h
Normal file
7
include/JTest/TestFunction.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef JTEST__TESTFUNCTION_H__
|
||||
#define JTEST__TESTFUNCTION_H__
|
||||
#include <functional>
|
||||
namespace JTest {
|
||||
typedef std::function<void()> test_fn;
|
||||
}
|
||||
#endif
|
||||
12
include/JTest/TestOptions.h
Normal file
12
include/JTest/TestOptions.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef JTEST__TESTOPTIONS_H__
|
||||
#define JTEST__TESTOPTIONS_H__
|
||||
namespace JTest {
|
||||
class TestOptions;
|
||||
}
|
||||
|
||||
namespace JTest {
|
||||
class TestOptions {
|
||||
|
||||
};
|
||||
}
|
||||
#endif
|
||||
17
include/JTest/TestPendingException.h
Normal file
17
include/JTest/TestPendingException.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef JTEST__TESTPENDINGEXCEPTION_H__
|
||||
#define JTEST__TESTPENDINGEXCEPTION_H__
|
||||
namespace JTest {
|
||||
class TestPendingException;
|
||||
}
|
||||
#include <JTest/TestException.h>
|
||||
#include <JTest/TestStatus.h>
|
||||
|
||||
namespace JTest {
|
||||
class TestPendingException: public TestException {
|
||||
public:
|
||||
TestPendingException(const std::string& label, const std::string& path, const std::string& reason);
|
||||
virtual ~TestPendingException();
|
||||
virtual TestStatus status() const;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
28
include/JTest/TestResults.h
Normal file
28
include/JTest/TestResults.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef JTEST_TESTRESULTS_H__
|
||||
#define JTEST_TESTRESULTS_H__
|
||||
namespace JTest {
|
||||
class TestResults;
|
||||
}
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
namespace JTest {
|
||||
// TODO: Consider hiding the vectors behind accessor methods void add(...), T get(), vector<T> get(uint32_t index)
|
||||
class TestResults {
|
||||
public:
|
||||
TestResults();
|
||||
TestResults(uint32_t total, uint32_t skipped, uint32_t passed, uint32_t failed);
|
||||
TestResults operator+(const TestResults& right) const;
|
||||
TestResults& operator+=(const TestResults& right);
|
||||
friend std::ostream& operator<<(std::ostream& os, const TestResults& results);
|
||||
// TODO: Make these private, make operator++ increment total, add methods pass, fail, and skip to increment passed, failed, and skipped
|
||||
uint32_t total;
|
||||
uint32_t skipped;
|
||||
uint32_t passed;
|
||||
uint32_t failed;
|
||||
private:
|
||||
// vector<TestError> errors;
|
||||
// vector<TestFailure> failures;
|
||||
// vector<TestSkip> skipped;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
11
include/JTest/TestStatus.h
Normal file
11
include/JTest/TestStatus.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef JTEST__TESTSTATUS_H__
|
||||
#define JTEST__TESTSTATUS_H__
|
||||
namespace JTest {
|
||||
enum TestStatus {
|
||||
Unknown,
|
||||
Passed,
|
||||
Failed,
|
||||
Skipped,
|
||||
};
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user