• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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