1 // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
2 // Distributed under MIT license, or public domain if desired and
3 // recognized in your jurisdiction.
4 // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
5
6 #define _CRT_SECURE_NO_WARNINGS 1 // Prevents deprecation warning with MSVC
7 #include "jsontest.h"
8 #include <cstdio>
9 #include <string>
10
11 #if defined(_MSC_VER)
12 // Used to install a report hook that prevent dialog on assertion and error.
13 #include <crtdbg.h>
14 #endif // if defined(_MSC_VER)
15
16 #if defined(_WIN32)
17 // Used to prevent dialog on memory fault.
18 // Limits headers included by Windows.h
19 #define WIN32_LEAN_AND_MEAN
20 #define NOSERVICE
21 #define NOMCX
22 #define NOIME
23 #define NOSOUND
24 #define NOCOMM
25 #define NORPC
26 #define NOGDI
27 #define NOUSER
28 #define NODRIVERS
29 #define NOLOGERROR
30 #define NOPROFILER
31 #define NOMEMMGR
32 #define NOLFILEIO
33 #define NOOPENFILE
34 #define NORESOURCE
35 #define NOATOM
36 #define NOLANGUAGE
37 #define NOLSTRING
38 #define NODBCS
39 #define NOKEYBOARDINFO
40 #define NOGDICAPMASKS
41 #define NOCOLOR
42 #define NOGDIOBJ
43 #define NODRAWTEXT
44 #define NOTEXTMETRIC
45 #define NOSCALABLEFONT
46 #define NOBITMAP
47 #define NORASTEROPS
48 #define NOMETAFILE
49 #define NOSYSMETRICS
50 #define NOSYSTEMPARAMSINFO
51 #define NOMSG
52 #define NOWINSTYLES
53 #define NOWINOFFSETS
54 #define NOSHOWWINDOW
55 #define NODEFERWINDOWPOS
56 #define NOVIRTUALKEYCODES
57 #define NOKEYSTATES
58 #define NOWH
59 #define NOMENUS
60 #define NOSCROLL
61 #define NOCLIPBOARD
62 #define NOICONS
63 #define NOMB
64 #define NOSYSCOMMANDS
65 #define NOMDI
66 #define NOCTLMGR
67 #define NOWINMESSAGES
68 #include <windows.h>
69 #endif // if defined(_WIN32)
70
71 namespace JsonTest {
72
73 // class TestResult
74 // //////////////////////////////////////////////////////////////////
75
TestResult()76 TestResult::TestResult() {
77 // The root predicate has id 0
78 rootPredicateNode_.id_ = 0;
79 rootPredicateNode_.next_ = nullptr;
80 predicateStackTail_ = &rootPredicateNode_;
81 }
82
setTestName(const Json::String & name)83 void TestResult::setTestName(const Json::String& name) { name_ = name; }
84
85 TestResult&
addFailure(const char * file,unsigned int line,const char * expr)86 TestResult::addFailure(const char* file, unsigned int line, const char* expr) {
87 /// Walks the PredicateContext stack adding them to failures_ if not already
88 /// added.
89 unsigned int nestingLevel = 0;
90 PredicateContext* lastNode = rootPredicateNode_.next_;
91 for (; lastNode != nullptr; lastNode = lastNode->next_) {
92 if (lastNode->id_ > lastUsedPredicateId_) // new PredicateContext
93 {
94 lastUsedPredicateId_ = lastNode->id_;
95 addFailureInfo(lastNode->file_, lastNode->line_, lastNode->expr_,
96 nestingLevel);
97 // Link the PredicateContext to the failure for message target when
98 // popping the PredicateContext.
99 lastNode->failure_ = &(failures_.back());
100 }
101 ++nestingLevel;
102 }
103
104 // Adds the failed assertion
105 addFailureInfo(file, line, expr, nestingLevel);
106 messageTarget_ = &(failures_.back());
107 return *this;
108 }
109
addFailureInfo(const char * file,unsigned int line,const char * expr,unsigned int nestingLevel)110 void TestResult::addFailureInfo(const char* file,
111 unsigned int line,
112 const char* expr,
113 unsigned int nestingLevel) {
114 Failure failure;
115 failure.file_ = file;
116 failure.line_ = line;
117 if (expr) {
118 failure.expr_ = expr;
119 }
120 failure.nestingLevel_ = nestingLevel;
121 failures_.push_back(failure);
122 }
123
popPredicateContext()124 TestResult& TestResult::popPredicateContext() {
125 PredicateContext* lastNode = &rootPredicateNode_;
126 while (lastNode->next_ != nullptr && lastNode->next_->next_ != nullptr) {
127 lastNode = lastNode->next_;
128 }
129 // Set message target to popped failure
130 PredicateContext* tail = lastNode->next_;
131 if (tail != nullptr && tail->failure_ != nullptr) {
132 messageTarget_ = tail->failure_;
133 }
134 // Remove tail from list
135 predicateStackTail_ = lastNode;
136 lastNode->next_ = nullptr;
137 return *this;
138 }
139
failed() const140 bool TestResult::failed() const { return !failures_.empty(); }
141
printFailure(bool printTestName) const142 void TestResult::printFailure(bool printTestName) const {
143 if (failures_.empty()) {
144 return;
145 }
146
147 if (printTestName) {
148 printf("* Detail of %s test failure:\n", name_.c_str());
149 }
150
151 // Print in reverse to display the callstack in the right order
152 for (const auto& failure : failures_) {
153 Json::String indent(failure.nestingLevel_ * 2, ' ');
154 if (failure.file_) {
155 printf("%s%s(%u): ", indent.c_str(), failure.file_, failure.line_);
156 }
157 if (!failure.expr_.empty()) {
158 printf("%s\n", failure.expr_.c_str());
159 } else if (failure.file_) {
160 printf("\n");
161 }
162 if (!failure.message_.empty()) {
163 Json::String reindented = indentText(failure.message_, indent + " ");
164 printf("%s\n", reindented.c_str());
165 }
166 }
167 }
168
indentText(const Json::String & text,const Json::String & indent)169 Json::String TestResult::indentText(const Json::String& text,
170 const Json::String& indent) {
171 Json::String reindented;
172 Json::String::size_type lastIndex = 0;
173 while (lastIndex < text.size()) {
174 Json::String::size_type nextIndex = text.find('\n', lastIndex);
175 if (nextIndex == Json::String::npos) {
176 nextIndex = text.size() - 1;
177 }
178 reindented += indent;
179 reindented += text.substr(lastIndex, nextIndex - lastIndex + 1);
180 lastIndex = nextIndex + 1;
181 }
182 return reindented;
183 }
184
addToLastFailure(const Json::String & message)185 TestResult& TestResult::addToLastFailure(const Json::String& message) {
186 if (messageTarget_ != nullptr) {
187 messageTarget_->message_ += message;
188 }
189 return *this;
190 }
191
operator <<(Json::Int64 value)192 TestResult& TestResult::operator<<(Json::Int64 value) {
193 return addToLastFailure(Json::valueToString(value));
194 }
195
operator <<(Json::UInt64 value)196 TestResult& TestResult::operator<<(Json::UInt64 value) {
197 return addToLastFailure(Json::valueToString(value));
198 }
199
operator <<(bool value)200 TestResult& TestResult::operator<<(bool value) {
201 return addToLastFailure(value ? "true" : "false");
202 }
203
204 // class TestCase
205 // //////////////////////////////////////////////////////////////////
206
207 TestCase::TestCase() = default;
208
209 TestCase::~TestCase() = default;
210
run(TestResult & result)211 void TestCase::run(TestResult& result) {
212 result_ = &result;
213 runTestCase();
214 }
215
216 // class Runner
217 // //////////////////////////////////////////////////////////////////
218
219 Runner::Runner() = default;
220
add(TestCaseFactory factory)221 Runner& Runner::add(TestCaseFactory factory) {
222 tests_.push_back(factory);
223 return *this;
224 }
225
testCount() const226 size_t Runner::testCount() const { return tests_.size(); }
227
testNameAt(size_t index) const228 Json::String Runner::testNameAt(size_t index) const {
229 TestCase* test = tests_[index]();
230 Json::String name = test->testName();
231 delete test;
232 return name;
233 }
234
runTestAt(size_t index,TestResult & result) const235 void Runner::runTestAt(size_t index, TestResult& result) const {
236 TestCase* test = tests_[index]();
237 result.setTestName(test->testName());
238 printf("Testing %s: ", test->testName());
239 fflush(stdout);
240 #if JSON_USE_EXCEPTION
241 try {
242 #endif // if JSON_USE_EXCEPTION
243 test->run(result);
244 #if JSON_USE_EXCEPTION
245 } catch (const std::exception& e) {
246 result.addFailure(__FILE__, __LINE__, "Unexpected exception caught:")
247 << e.what();
248 }
249 #endif // if JSON_USE_EXCEPTION
250 delete test;
251 const char* status = result.failed() ? "FAILED" : "OK";
252 printf("%s\n", status);
253 fflush(stdout);
254 }
255
runAllTest(bool printSummary) const256 bool Runner::runAllTest(bool printSummary) const {
257 size_t const count = testCount();
258 std::deque<TestResult> failures;
259 for (size_t index = 0; index < count; ++index) {
260 TestResult result;
261 runTestAt(index, result);
262 if (result.failed()) {
263 failures.push_back(result);
264 }
265 }
266
267 if (failures.empty()) {
268 if (printSummary) {
269 printf("All %zu tests passed\n", count);
270 }
271 return true;
272 } else {
273 for (auto& result : failures) {
274 result.printFailure(count > 1);
275 }
276
277 if (printSummary) {
278 size_t const failedCount = failures.size();
279 size_t const passedCount = count - failedCount;
280 printf("%zu/%zu tests passed (%zu failure(s))\n", passedCount, count,
281 failedCount);
282 }
283 return false;
284 }
285 }
286
testIndex(const Json::String & testName,size_t & indexOut) const287 bool Runner::testIndex(const Json::String& testName, size_t& indexOut) const {
288 const size_t count = testCount();
289 for (size_t index = 0; index < count; ++index) {
290 if (testNameAt(index) == testName) {
291 indexOut = index;
292 return true;
293 }
294 }
295 return false;
296 }
297
listTests() const298 void Runner::listTests() const {
299 const size_t count = testCount();
300 for (size_t index = 0; index < count; ++index) {
301 printf("%s\n", testNameAt(index).c_str());
302 }
303 }
304
runCommandLine(int argc,const char * argv[]) const305 int Runner::runCommandLine(int argc, const char* argv[]) const {
306 // typedef std::deque<String> TestNames;
307 Runner subrunner;
308 for (int index = 1; index < argc; ++index) {
309 Json::String opt = argv[index];
310 if (opt == "--list-tests") {
311 listTests();
312 return 0;
313 } else if (opt == "--test-auto") {
314 preventDialogOnCrash();
315 } else if (opt == "--test") {
316 ++index;
317 if (index < argc) {
318 size_t testNameIndex;
319 if (testIndex(argv[index], testNameIndex)) {
320 subrunner.add(tests_[testNameIndex]);
321 } else {
322 fprintf(stderr, "Test '%s' does not exist!\n", argv[index]);
323 return 2;
324 }
325 } else {
326 printUsage(argv[0]);
327 return 2;
328 }
329 } else {
330 printUsage(argv[0]);
331 return 2;
332 }
333 }
334 bool succeeded;
335 if (subrunner.testCount() > 0) {
336 succeeded = subrunner.runAllTest(subrunner.testCount() > 1);
337 } else {
338 succeeded = runAllTest(true);
339 }
340 return succeeded ? 0 : 1;
341 }
342
343 #if defined(_MSC_VER) && defined(_DEBUG)
344 // Hook MSVCRT assertions to prevent dialog from appearing
345 static int
msvcrtSilentReportHook(int reportType,char * message,int *)346 msvcrtSilentReportHook(int reportType, char* message, int* /*returnValue*/) {
347 // The default CRT handling of error and assertion is to display
348 // an error dialog to the user.
349 // Instead, when an error or an assertion occurs, we force the
350 // application to terminate using abort() after display
351 // the message on stderr.
352 if (reportType == _CRT_ERROR || reportType == _CRT_ASSERT) {
353 // calling abort() cause the ReportHook to be called
354 // The following is used to detect this case and let's the
355 // error handler fallback on its default behaviour (
356 // display a warning message)
357 static volatile bool isAborting = false;
358 if (isAborting) {
359 return TRUE;
360 }
361 isAborting = true;
362
363 fprintf(stderr, "CRT Error/Assert:\n%s\n", message);
364 fflush(stderr);
365 abort();
366 }
367 // Let's other reportType (_CRT_WARNING) be handled as they would by default
368 return FALSE;
369 }
370 #endif // if defined(_MSC_VER)
371
preventDialogOnCrash()372 void Runner::preventDialogOnCrash() {
373 #if defined(_MSC_VER) && defined(_DEBUG)
374 // Install a hook to prevent MSVCRT error and assertion from
375 // popping a dialog
376 // This function a NO-OP in release configuration
377 // (which cause warning since msvcrtSilentReportHook is not referenced)
378 _CrtSetReportHook(&msvcrtSilentReportHook);
379 #endif // if defined(_MSC_VER)
380
381 // @todo investigate this handler (for buffer overflow)
382 // _set_security_error_handler
383
384 #if defined(_WIN32)
385 // Prevents the system from popping a dialog for debugging if the
386 // application fails due to invalid memory access.
387 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX |
388 SEM_NOOPENFILEERRORBOX);
389 #endif // if defined(_WIN32)
390 }
391
printUsage(const char * appName)392 void Runner::printUsage(const char* appName) {
393 printf("Usage: %s [options]\n"
394 "\n"
395 "If --test is not specified, then all the test cases be run.\n"
396 "\n"
397 "Valid options:\n"
398 "--list-tests: print the name of all test cases on the standard\n"
399 " output and exit.\n"
400 "--test TESTNAME: executes the test case with the specified name.\n"
401 " May be repeated.\n"
402 "--test-auto: prevent dialog prompting for debugging on crash.\n",
403 appName);
404 }
405
406 // Assertion functions
407 // //////////////////////////////////////////////////////////////////
408
ToJsonString(const char * toConvert)409 Json::String ToJsonString(const char* toConvert) {
410 return Json::String(toConvert);
411 }
412
ToJsonString(Json::String in)413 Json::String ToJsonString(Json::String in) { return in; }
414
415 #if JSONCPP_USING_SECURE_MEMORY
ToJsonString(std::string in)416 Json::String ToJsonString(std::string in) {
417 return Json::String(in.data(), in.data() + in.length());
418 }
419 #endif
420
checkStringEqual(TestResult & result,const Json::String & expected,const Json::String & actual,const char * file,unsigned int line,const char * expr)421 TestResult& checkStringEqual(TestResult& result,
422 const Json::String& expected,
423 const Json::String& actual,
424 const char* file,
425 unsigned int line,
426 const char* expr) {
427 if (expected != actual) {
428 result.addFailure(file, line, expr);
429 result << "Expected: '" << expected << "'\n";
430 result << "Actual : '" << actual << "'";
431 }
432 return result;
433 }
434
435 } // namespace JsonTest
436