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
addFailure(const char * file,unsigned int line,const char * expr)85 TestResult& TestResult::addFailure(const char* file, unsigned int line,
86 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, unsigned int line,
111 const char* expr, unsigned int nestingLevel) {
112 Failure failure;
113 failure.file_ = file;
114 failure.line_ = line;
115 if (expr) {
116 failure.expr_ = expr;
117 }
118 failure.nestingLevel_ = nestingLevel;
119 failures_.push_back(failure);
120 }
121
popPredicateContext()122 TestResult& TestResult::popPredicateContext() {
123 PredicateContext* lastNode = &rootPredicateNode_;
124 while (lastNode->next_ != nullptr && lastNode->next_->next_ != nullptr) {
125 lastNode = lastNode->next_;
126 }
127 // Set message target to popped failure
128 PredicateContext* tail = lastNode->next_;
129 if (tail != nullptr && tail->failure_ != nullptr) {
130 messageTarget_ = tail->failure_;
131 }
132 // Remove tail from list
133 predicateStackTail_ = lastNode;
134 lastNode->next_ = nullptr;
135 return *this;
136 }
137
failed() const138 bool TestResult::failed() const { return !failures_.empty(); }
139
printFailure(bool printTestName) const140 void TestResult::printFailure(bool printTestName) const {
141 if (failures_.empty()) {
142 return;
143 }
144
145 if (printTestName) {
146 printf("* Detail of %s test failure:\n", name_.c_str());
147 }
148
149 // Print in reverse to display the callstack in the right order
150 for (const auto& failure : failures_) {
151 Json::String indent(failure.nestingLevel_ * 2, ' ');
152 if (failure.file_) {
153 printf("%s%s(%u): ", indent.c_str(), failure.file_, failure.line_);
154 }
155 if (!failure.expr_.empty()) {
156 printf("%s\n", failure.expr_.c_str());
157 } else if (failure.file_) {
158 printf("\n");
159 }
160 if (!failure.message_.empty()) {
161 Json::String reindented = indentText(failure.message_, indent + " ");
162 printf("%s\n", reindented.c_str());
163 }
164 }
165 }
166
indentText(const Json::String & text,const Json::String & indent)167 Json::String TestResult::indentText(const Json::String& text,
168 const Json::String& indent) {
169 Json::String reindented;
170 Json::String::size_type lastIndex = 0;
171 while (lastIndex < text.size()) {
172 Json::String::size_type nextIndex = text.find('\n', lastIndex);
173 if (nextIndex == Json::String::npos) {
174 nextIndex = text.size() - 1;
175 }
176 reindented += indent;
177 reindented += text.substr(lastIndex, nextIndex - lastIndex + 1);
178 lastIndex = nextIndex + 1;
179 }
180 return reindented;
181 }
182
addToLastFailure(const Json::String & message)183 TestResult& TestResult::addToLastFailure(const Json::String& message) {
184 if (messageTarget_ != nullptr) {
185 messageTarget_->message_ += message;
186 }
187 return *this;
188 }
189
operator <<(Json::Int64 value)190 TestResult& TestResult::operator<<(Json::Int64 value) {
191 return addToLastFailure(Json::valueToString(value));
192 }
193
operator <<(Json::UInt64 value)194 TestResult& TestResult::operator<<(Json::UInt64 value) {
195 return addToLastFailure(Json::valueToString(value));
196 }
197
operator <<(bool value)198 TestResult& TestResult::operator<<(bool value) {
199 return addToLastFailure(value ? "true" : "false");
200 }
201
202 // class TestCase
203 // //////////////////////////////////////////////////////////////////
204
205 TestCase::TestCase() = default;
206
207 TestCase::~TestCase() = default;
208
run(TestResult & result)209 void TestCase::run(TestResult& result) {
210 result_ = &result;
211 runTestCase();
212 }
213
214 // class Runner
215 // //////////////////////////////////////////////////////////////////
216
217 Runner::Runner() = default;
218
add(TestCaseFactory factory)219 Runner& Runner::add(TestCaseFactory factory) {
220 tests_.push_back(factory);
221 return *this;
222 }
223
testCount() const224 size_t Runner::testCount() const { return tests_.size(); }
225
testNameAt(size_t index) const226 Json::String Runner::testNameAt(size_t index) const {
227 TestCase* test = tests_[index]();
228 Json::String name = test->testName();
229 delete test;
230 return name;
231 }
232
runTestAt(size_t index,TestResult & result) const233 void Runner::runTestAt(size_t index, TestResult& result) const {
234 TestCase* test = tests_[index]();
235 result.setTestName(test->testName());
236 printf("Testing %s: ", test->testName());
237 fflush(stdout);
238 #if JSON_USE_EXCEPTION
239 try {
240 #endif // if JSON_USE_EXCEPTION
241 test->run(result);
242 #if JSON_USE_EXCEPTION
243 } catch (const std::exception& e) {
244 result.addFailure(__FILE__, __LINE__, "Unexpected exception caught:")
245 << e.what();
246 }
247 #endif // if JSON_USE_EXCEPTION
248 delete test;
249 const char* status = result.failed() ? "FAILED" : "OK";
250 printf("%s\n", status);
251 fflush(stdout);
252 }
253
runAllTest(bool printSummary) const254 bool Runner::runAllTest(bool printSummary) const {
255 size_t const count = testCount();
256 std::deque<TestResult> failures;
257 for (size_t index = 0; index < count; ++index) {
258 TestResult result;
259 runTestAt(index, result);
260 if (result.failed()) {
261 failures.push_back(result);
262 }
263 }
264
265 if (failures.empty()) {
266 if (printSummary) {
267 printf("All %zu tests passed\n", count);
268 }
269 return true;
270 }
271 for (auto& result : failures) {
272 result.printFailure(count > 1);
273 }
274
275 if (printSummary) {
276 size_t const failedCount = failures.size();
277 size_t const passedCount = count - failedCount;
278 printf("%zu/%zu tests passed (%zu failure(s))\n", passedCount, count,
279 failedCount);
280 }
281 return false;
282 }
283
testIndex(const Json::String & testName,size_t & indexOut) const284 bool Runner::testIndex(const Json::String& testName, size_t& indexOut) const {
285 const size_t count = testCount();
286 for (size_t index = 0; index < count; ++index) {
287 if (testNameAt(index) == testName) {
288 indexOut = index;
289 return true;
290 }
291 }
292 return false;
293 }
294
listTests() const295 void Runner::listTests() const {
296 const size_t count = testCount();
297 for (size_t index = 0; index < count; ++index) {
298 printf("%s\n", testNameAt(index).c_str());
299 }
300 }
301
runCommandLine(int argc,const char * argv[]) const302 int Runner::runCommandLine(int argc, const char* argv[]) const {
303 // typedef std::deque<String> TestNames;
304 Runner subrunner;
305 for (int index = 1; index < argc; ++index) {
306 Json::String opt = argv[index];
307 if (opt == "--list-tests") {
308 listTests();
309 return 0;
310 }
311 if (opt == "--test-auto") {
312 preventDialogOnCrash();
313 } else if (opt == "--test") {
314 ++index;
315 if (index < argc) {
316 size_t testNameIndex;
317 if (testIndex(argv[index], testNameIndex)) {
318 subrunner.add(tests_[testNameIndex]);
319 } else {
320 fprintf(stderr, "Test '%s' does not exist!\n", argv[index]);
321 return 2;
322 }
323 } else {
324 printUsage(argv[0]);
325 return 2;
326 }
327 } else {
328 printUsage(argv[0]);
329 return 2;
330 }
331 }
332 bool succeeded;
333 if (subrunner.testCount() > 0) {
334 succeeded = subrunner.runAllTest(subrunner.testCount() > 1);
335 } else {
336 succeeded = runAllTest(true);
337 }
338 return succeeded ? 0 : 1;
339 }
340
341 #if defined(_MSC_VER) && defined(_DEBUG)
342 // Hook MSVCRT assertions to prevent dialog from appearing
msvcrtSilentReportHook(int reportType,char * message,int *)343 static int msvcrtSilentReportHook(int reportType, char* message,
344 int* /*returnValue*/) {
345 // The default CRT handling of error and assertion is to display
346 // an error dialog to the user.
347 // Instead, when an error or an assertion occurs, we force the
348 // application to terminate using abort() after display
349 // the message on stderr.
350 if (reportType == _CRT_ERROR || reportType == _CRT_ASSERT) {
351 // calling abort() cause the ReportHook to be called
352 // The following is used to detect this case and let's the
353 // error handler fallback on its default behaviour (
354 // display a warning message)
355 static volatile bool isAborting = false;
356 if (isAborting) {
357 return TRUE;
358 }
359 isAborting = true;
360
361 fprintf(stderr, "CRT Error/Assert:\n%s\n", message);
362 fflush(stderr);
363 abort();
364 }
365 // Let's other reportType (_CRT_WARNING) be handled as they would by default
366 return FALSE;
367 }
368 #endif // if defined(_MSC_VER)
369
preventDialogOnCrash()370 void Runner::preventDialogOnCrash() {
371 #if defined(_MSC_VER) && defined(_DEBUG)
372 // Install a hook to prevent MSVCRT error and assertion from
373 // popping a dialog
374 // This function a NO-OP in release configuration
375 // (which cause warning since msvcrtSilentReportHook is not referenced)
376 _CrtSetReportHook(&msvcrtSilentReportHook);
377 #endif // if defined(_MSC_VER)
378
379 // @todo investigate this handler (for buffer overflow)
380 // _set_security_error_handler
381
382 #if defined(_WIN32)
383 // Prevents the system from popping a dialog for debugging if the
384 // application fails due to invalid memory access.
385 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX |
386 SEM_NOOPENFILEERRORBOX);
387 #endif // if defined(_WIN32)
388 }
389
printUsage(const char * appName)390 void Runner::printUsage(const char* appName) {
391 printf("Usage: %s [options]\n"
392 "\n"
393 "If --test is not specified, then all the test cases be run.\n"
394 "\n"
395 "Valid options:\n"
396 "--list-tests: print the name of all test cases on the standard\n"
397 " output and exit.\n"
398 "--test TESTNAME: executes the test case with the specified name.\n"
399 " May be repeated.\n"
400 "--test-auto: prevent dialog prompting for debugging on crash.\n",
401 appName);
402 }
403
404 // Assertion functions
405 // //////////////////////////////////////////////////////////////////
406
ToJsonString(const char * toConvert)407 Json::String ToJsonString(const char* toConvert) {
408 return Json::String(toConvert);
409 }
410
ToJsonString(Json::String in)411 Json::String ToJsonString(Json::String in) { return in; }
412
413 #if JSONCPP_USING_SECURE_MEMORY
ToJsonString(std::string in)414 Json::String ToJsonString(std::string in) {
415 return Json::String(in.data(), in.data() + in.length());
416 }
417 #endif
418
checkStringEqual(TestResult & result,const Json::String & expected,const Json::String & actual,const char * file,unsigned int line,const char * expr)419 TestResult& checkStringEqual(TestResult& result, const Json::String& expected,
420 const Json::String& actual, const char* file,
421 unsigned int line, const char* expr) {
422 if (expected != actual) {
423 result.addFailure(file, line, expr);
424 result << "Expected: '" << expected << "'\n";
425 result << "Actual : '" << actual << "'";
426 }
427 return result;
428 }
429
430 } // namespace JsonTest
431