-
Notifications
You must be signed in to change notification settings - Fork 1
/
gtest_ext.h
374 lines (348 loc) · 14.1 KB
/
gtest_ext.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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
// This file contains implementations that extend the functionality of the
// google test framework
// Version: 0.1.1-beta
#include <gtest/gtest.h>
#include <random>
#include <string>
#include <iomanip>
#include <future>
#include <chrono>
#include <map>
#include <fstream>
#include "termcolor/termcolor.hpp"
class SkipListener : public ::testing::EmptyTestEventListener
{
private:
size_t fatal_failures{0};
// Fired before each individual test starts: before the test fixture is constructed
// and SetUp() is invoked.
void OnTestStart(const ::testing::TestInfo& info) override
{
if (fatal_failures > 0) {
GTEST_SKIP() << "Test skipped until other errors are fixed";
}
}
// Fired after the unit test runs.
void OnTestPartResult(const ::testing::TestPartResult& result) override {
if(result.failed()) {
fatal_failures++;
}
}
};
class UnitTestFileManager : public ::testing::Test {
public:
UnitTestFileManager(const std::string &filename): filename_(filename) { }
protected:
void SetUp() override {
std::ifstream my_empty_file;
my_empty_file.open(filename_);
if (my_empty_file.good()) {
rename(filename_.c_str(), (PREFIX + filename_).c_str());
file_exists = true;
}
my_empty_file.close();
}
void TearDown() override {
if (file_exists) {
rename((PREFIX + filename_).c_str(), filename_.c_str());
} else {
remove(filename_.c_str());
}
}
const std::string PREFIX{"u_test"};
bool file_exists = false;
std::string filename_;
};
// Run and retrieves the output of an executable program from
// the command line.
//
// @param prog_name name of the executable file
// @param input keyboard input sent to the program
//
// @return output of the program
std::string exec_program(std::string prog_name, std::string input)
{
FILE *fp = popen(("echo \""+ input +"\" | ./" + prog_name).c_str(), "r");
char buf[1024];
std::string output = "";
while (fgets(buf, 1024, fp)) {
output += buf;
}
pclose(fp);
return output;
}
// Converts a double value to a formatted string
//
// @param val value to be formatted
// @param prec decimal precision
//
// @return string representation of the double value
std::string to_string_double(double val, const int prec = 2)
{
std::ostringstream out;
out << std::fixed << std::setprecision(prec) << val;
return out.str();
}
// Generate a string with random values from an alphanumeric character set
//
// @param max_length length of string to generate
//
// @return randomly generated string
std::string generate_string(int max_length){
std::string possible_characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
std::random_device rd;
std::mt19937 engine(rd());
std::uniform_int_distribution<> dist(0, possible_characters.size()-1);
std::string ret = "";
for(int i = 0; i < max_length; i++){
int random_index = dist(engine); //get index between 0 and possible_characters.size()-1
ret += possible_characters[random_index];
}
return ret;
}
std::string expose_special_characters(const std::string &source) {
if(source.length() == 0) {
return "<empty>";
}
std::map<char, std::string> replacement_map {
{' ', "<space>"},
{'\n', "\\n\n"}
};
std::stringstream exposed_string;
for(int i = 0; i < source.length(); i++) {
if(replacement_map.find(source[i]) != replacement_map.end()) {
exposed_string << replacement_map[source[i]];
} else {
exposed_string << source[i];
}
}
return exposed_string.str();
}
// This macro is used to simulate the standard input (cin) for a code block
//
// @param input simulated input
// @param statement lambda code block to run
#define SIMULATE_CIN(input, statement) { \
std::stringstream input_ss, output_ss; \
auto old_output_buf = std::cout.rdbuf(output_ss.rdbuf()); \
auto old_input_buf = std::cin.rdbuf(input_ss.rdbuf()); \
input_ss.str(input); \
statement; \
std::cin.rdbuf(old_input_buf); \
std::cout.rdbuf(old_output_buf); \
}
::testing::AssertionResult AssertExecStdOut(const char* prog_name_expr,
const char* prog_input_expr,
const char* prog_output_expr,
std::string prog_name,
std::string prog_input,
std::string prog_output) {
if ( access( prog_name.c_str(), F_OK ) == -1 ) {
return ::testing::AssertionFailure() << " cannot test '" << prog_name
<< "': Make sure your executable file"
<< " is called '" << prog_name << "'";
}
std::string exec_output = exec_program(prog_name, prog_input);
if (exec_output == prog_output) {
return ::testing::AssertionSuccess();
} else {
int pos = 0;
int line_pos = 0;
int char_pos = 0;
std::string exec_diff = "";
std::string prog_diff = "";
bool found_diff = false;
for(; pos < exec_output.length(); pos++) {
if (exec_output[pos] == '\n') {
line_pos++;
char_pos = 0;
}
char_pos++;
exec_diff = exec_output[pos];
if (pos >= prog_output.length()) {
break;
} else if(prog_output[pos] != exec_output[pos]) {
prog_diff = prog_output[pos];
found_diff = true;
break;
}
}
if(!found_diff && pos < prog_output.length()) {
exec_diff = "";
prog_diff = prog_output[pos];
}
exec_diff = expose_special_characters(exec_diff);
prog_diff = expose_special_characters(prog_diff);
std::ostringstream error_str_stream;
std::ostringstream exec_str_stream;
exec_str_stream << termcolor::colorize << termcolor::green
<< exec_output.substr(0, pos)
<< termcolor::red << exec_output.substr(pos)
<< termcolor::reset;
std::ostringstream prog_str_stream;
prog_str_stream << termcolor::colorize << termcolor::green
<< prog_output.substr(0, pos)
<< termcolor::red << prog_output.substr(pos)
<< termcolor::reset;
error_str_stream << "Your program's output did not match the expected "
<< "output starting on line " << line_pos + 1
<< " character " << char_pos
<< ".\nExpected " << prog_diff
<< " instead of " << exec_diff
//<< "\n\nExpected output: \n" << expose_special_characters(prog_str_stream.str())
<< "\n\nExpected output: \n" << prog_str_stream.str()
<< "\n\nYour program's output: \n"
//<< expose_special_characters(exec_str_stream.str()) << "\n\nTest Input: \n"
<< exec_str_stream.str() << "\n\nInput: \n"
<< prog_input ;
return ::testing::AssertionFailure() << error_str_stream.str();
}
}
// This macro checks if the output of an executable program matches an expected
// output.
//
// @param prog_name name of the executable file
// @param input keyboard input sent to the program
// @param output expected output of the program
#define ASSERT_EXECEQ(prog_name, input, output) \
EXPECT_PRED_FORMAT3(AssertExecStdOut, prog_name, input, output)
template <typename T>
::testing::AssertionResult AssertExecMatcher(const char* prog_name_expr,
const char* prog_input_expr,
const char* matcher_expr,
std::string prog_name,
std::string prog_input,
T matcher) {
if ( access( prog_name.c_str(), F_OK ) == -1 ) {
return ::testing::AssertionFailure() << " cannot test '" << prog_name
<< "': Make sure your executable file"
<< " is called '" << prog_name << "'";
}
std::string exec_output = exec_program(prog_name, prog_input);
// based on https://github.com/google/googletest/blob/fb49e6c164490a227bbb7cf5223b846c836a0305/googlemock/include/gmock/gmock-matchers.h#L1304
// create a predicate formatter that can be used to run the matcher
auto pred_formatter = ::testing::internal::MakePredicateFormatterFromMatcher(matcher);
// where: matcher_expr is the stringized matcher (e.g., StartsWith("Hello"))
// exec_output is the expected output, in this case the program output
return pred_formatter(matcher_expr, exec_output) << "\n Input: "
<< prog_input;
}
// This macro checks if the output of an executable follows the pattern defined
// in the gMock matcher
// @param prog_name name of the executable file
// @param input keyboard input sent to the program
// @param matcher gMock matcher used to test the executable's output
#define ASSERT_EXECTHAT(prog_name, input, matcher) \
EXPECT_PRED_FORMAT3(AssertExecMatcher, prog_name, input, matcher)
::testing::AssertionResult AssertExecExit(const char* prog_name_expr,
const char* prog_input_expr,
const char* prog_max_dur,
std::string prog_name,
std::string prog_input,
int max_dur) {
if ( access( prog_name.c_str(), F_OK ) == -1 ) {
return ::testing::AssertionFailure() << " cannot test '" << prog_name
<< "': Make sure your executable file"
<< " is called '" << prog_name << "'";
}
std::promise<bool> completed;
auto stmt_future = completed.get_future();
std::thread([&](std::promise<bool>& completed) {
exec_program(prog_name, prog_input);
completed.set_value(true);
}, std::ref(completed)).detach();
if(stmt_future.wait_for(std::chrono::seconds(max_dur)) == std::future_status::timeout) {
return ::testing::AssertionFailure()
<< "Input: " << prog_input
<< "\n The program took more than " << max_dur
<< " seconds to exit. Check for infinite loops or "
<< "unnecessary inputs.";
} else {
return ::testing::AssertionSuccess();
}
}
// This macro checks whether the executable program exits given the provided
// input.
//
// @param prog_name name of the executable file
// @param input keyboard input sent to the program
// @param duration time in seconds to wait for program to exit, otherwise it is
// considered to have an infinite loop or requires addiitonal
// input
#define ASSERT_EXECEXIT(prog_name, input, duration) \
EXPECT_PRED_FORMAT3(AssertExecExit, prog_name, input, duration)
// Version of ASSERT_EXECIO_EQ that uses google mock's matchers
//
// @param prog_name name of the executable file
// @param input keyboard input sent to the program
// @param output expected output of the program
#define ASSERT_EXECIO_THAT(prog_name, input, matcher) {\
if ( access( prog_name, F_OK ) == -1 ) { \
GTEST_FATAL_FAILURE_(" cannot test '" prog_name "': no such file"); \
} \
ASSERT_THAT(main_output(prog_name, input), matcher) << " Input: " << input; \
}
// This macro simulates standard input and output when calling a given statement.
// The `input` parameter is routed to the simulated standard input stream (`std::cin`)
// so that the given statement receives it.
// The check block can contain statements that have access to the statement's output on the
// standard output (`std::cout`) using either the `SIO_OUT` or `your_output variables`.
//
// @param input input value
// @param stmt statement(s) performed
// @param check code block that has access to the programs output to `std::cout`
#define SIMULATE_SIO(input, stmt, check) { \
std::stringstream input_ss, output_ss; \
auto old_inputbuf = std::cin.rdbuf(input_ss.rdbuf()); \
auto old_outputbuf = std::cout.rdbuf(output_ss.rdbuf()); \
input_ss.str(input); \
stmt; \
std::cin.rdbuf(old_inputbuf); \
std::cout.rdbuf(old_outputbuf); \
const std::string SIO_OUT = output_ss.str(); \
const std::string SIO_IN = input, your_output = SIO_OUT; \
check; \
}
// Version of ASSERT_SIO_EQ that uses google mock's matchers
//
// @param expected expected string value
// @param stmt statement(s) performed
// deprecated by SIMULATE_SIO
#define ASSERT_SIO_THAT(input, expected, stmt) { \
std::stringstream input_ss, output_ss; \
auto old_inputbuf = std::cin.rdbuf(input_ss.rdbuf()); \
auto old_outputbuf = std::cout.rdbuf(output_ss.rdbuf()); \
input_ss.str(input); \
stmt; \
std::cin.rdbuf(old_inputbuf); \
std::cout.rdbuf(old_outputbuf); \
std::string your_output = output_ss.str(); \
ASSERT_THAT(your_output, expected); \
}
// This macro checks whether a function executes within a given time
//
// A thread is created to run the statement and update the status of a promise
// object. A future object is created from the promise object to check whether
// the promise object's value was updated within the specified duration. If the
// promise's value is not changed in time, the function is considered to have
// timed out.
//
// Code based on: http://antonlipov.blogspot.com/2015/08/how-to-timeout-tests-in-gtest.html
//
// @param secs seconds to wait before statement is considered to have
// timed out
// @param stmt statement to be tested
#define ASSERT_DURATION_LE(secs, stmt) { \
std::promise<bool> completed; \
auto stmt_future = completed.get_future(); \
std::thread([&](std::promise<bool>& completed) { \
stmt; \
completed.set_value(true); \
}, std::ref(completed)).detach(); \
if(stmt_future.wait_for(std::chrono::seconds(secs)) == std::future_status::timeout) \
if (!::testing::Test::HasFatalFailure()) { \
GTEST_FATAL_FAILURE_(" Your program took more than " #secs \
" seconds to exit. Check for infinite loops or unnecessary inputs."); \
} \
if (::testing::Test::HasFatalFailure()) FAIL(); \
}