-
Notifications
You must be signed in to change notification settings - Fork 0
/
UnitTesting.h
219 lines (180 loc) · 7.09 KB
/
UnitTesting.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//
// Author Wild Coast Solutions
// David Hamilton
//
// This file is distributed under the MIT License. See the accompanying file
// LICENSE.txt or http://www.opensource.org/licenses/mit-license.php for terms
// and conditions.
//
// This file contains an implementation of a simple unit testing framework. It uses a class
// to hold information about the running tests, and macros to help with easy testing.
//
// Project url: https://github.com/WildCoastSolutions/UnitTesting
#ifndef WILD_UNITTESTING_H
#define WILD_UNITTESTING_H
#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>
#include <mutex>
#include <functional>
#include <thread>
namespace Wild
{
namespace UnitTesting
{
// Class to record our testing progress, i.e. number of passed and failed tests.
// In theory many instances of this could be used to track different types of tests but
// for now it's just designed to be used once
class Test
{
public:
Test() : passed(0), failed(0), total(0)
{
}
Test(const Test&) = delete;
// Test whether a boolean condition is true or false and handle the result
void Assert(bool test, const std::string &file, int line, const std::string &details = 0)
{
test ? Pass() : Fail(file, line, details);
}
// Mark a test as passed
void Pass()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
passed++;
total++;
}
// Mark a test as failed and output useful info about where and what failed
void Fail(const std::string &file, int line, const std::string &details)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
std::stringstream output;
#ifdef WILD_UNITTESTING_SHOW_FAILURE_DETAILS
output << file << "(" << line << "): test failed, " << details << std::endl;
#else
output << file << "(" << line << "): test failed" << std::endl;
#endif
std::cout << output.str() << std::flush;
#ifdef WILD_UNITTESTING_BREAK_ON_FAIL
// This is designed for when the tests are failing and you want to look at the call stack
// in the debugger to see what's going on
std::abort();
#endif
failed++;
total++;
}
void Results()
{
std::cout << passed << " passed, " << failed << " failed, " << total << " total" << std::endl;
}
// Running counts for relevant stats. We'll leave them public and trust the user application not to modify them.
int passed;
int failed;
int total;
std::recursive_mutex mutex; // Protect stdout and counts in Pass & Fail from being written to from different threads
};
// Make sure that each file that includes this header is using the same object.
// This is useful to keep a global count of passed and failed tests across all files.
class AllTests : public Test
{
public:
AllTests(){}
static AllTests& instance()
{
static AllTests instance;
return instance;
}
};
// Helper functions in case the user application wants to use these values
inline int Passed() { return AllTests::instance().passed; }
inline int Failed() { return AllTests::instance().failed; }
inline int Total() { return AllTests::instance().total; }
}
}
// Macros to facilitate the testing process
#define Pass() { \
Wild::UnitTesting::AllTests::instance().Pass(); \
}
#define AssertTrueWithDetails(x, details){ \
Wild::UnitTesting::AllTests::instance().Assert(x, __FILE__, __LINE__, details); \
}
#define Fail(details) { \
Wild::UnitTesting::AllTests::instance().Fail(__FILE__, __LINE__, details); \
}
#ifdef WILD_UNITTESTING_SHOW_FAILURE_DETAILS
#define InternalAssertEquals(x, y) { \
std::stringstream wildUnitTestingSs; \
wildUnitTestingSs << "(" << #x << " == " << #y <<")" \
<< ", actual comparison performed (" << x << " == " << y << ")"; \
AssertTrueWithDetails(x == y, wildUnitTestingSs.str()); \
wildUnitTestingSs.str(""); \
}
#else
#define InternalAssertEquals(x, y){ \
AssertTrueWithDetails(x == y, "no details"); \
}
#endif
// Macros for use by user applications
#define AssertTrue(x) { \
AssertTrueWithDetails(x, #x " does not evaluate to true"); \
}
#define AssertFalse(x) { \
AssertTrueWithDetails(!x, #x " evaluates to true"); \
}
#define AssertEquals(x, y) { \
InternalAssertEquals(x, y) \
}
#define AssertThrows(code, ex) { \
try \
{ \
code; \
Fail(""); \
} \
catch (ex){ Pass(); } \
catch (...){ Fail(""); } \
}
#define AssertPrints(code, x) { \
std::lock_guard<std::recursive_mutex> wildUnitTestingLock(Wild::UnitTesting::AllTests::instance().mutex); \
std::stringstream wildUnitTestingOutput; \
std::streambuf* wildUnitTestingOriginal = std::cout.rdbuf(wildUnitTestingOutput.rdbuf()); \
code; \
std::string wildUnitTestingOutputText = wildUnitTestingOutput.str(); \
wildUnitTestingOutput.str(""); \
std::cout.rdbuf(wildUnitTestingOriginal); \
InternalAssertEquals(wildUnitTestingOutputText, x) \
}
#define AssertPrintsToStderr(code, x) { \
std::lock_guard<std::recursive_mutex> wildUnitTestingLock(Wild::UnitTesting::AllTests::instance().mutex); \
std::stringstream wildUnitTestingOutput; \
std::streambuf* wildUnitTestingOriginal = std::cerr.rdbuf(wildUnitTestingOutput.rdbuf()); \
code; \
std::string wildUnitTestingOutputText = wildUnitTestingOutput.str(); \
wildUnitTestingOutput.str(""); \
std::cerr.rdbuf(wildUnitTestingOriginal); \
InternalAssertEquals(wildUnitTestingOutputText, x) \
}
#define AssertThreadSafe(code, count) {\
std::lock_guard<std::recursive_mutex> wildUnitTestingLock(Wild::UnitTesting::AllTests::instance().mutex); \
std::stringstream wildUnitTestingOutput; \
std::streambuf* wildUnitTestingOriginal = std::cout.rdbuf(wildUnitTestingOutput.rdbuf()); \
std::function<void()> f = [](){ \
for (int i = 0; i < count; i++) { \
code; \
} \
}; \
std::thread t1(f); \
std::thread t2(f); \
std::thread t3(f); \
t3.join(); \
t2.join(); \
t1.join(); \
std::string wildUnitTestingOutputText = wildUnitTestingOutput.str(); \
wildUnitTestingOutput.str(""); \
std::cout.rdbuf(wildUnitTestingOriginal); \
}
#define EndTest { \
std::lock_guard<std::recursive_mutex> wildUnitTestingLock(Wild::UnitTesting::AllTests::instance().mutex); \
Wild::UnitTesting::AllTests::instance().Results(); return Wild::UnitTesting::AllTests::instance().failed; \
}
#endif