You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

450 rivejä
14 KiB

  1. //
  2. // Lol Engine — Unit test implementation
  3. //
  4. // Copyright © 2010—2016 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // Lol Engine is free software. It comes without any warranty, to
  7. // the extent permitted by applicable law. You can redistribute it
  8. // and/or modify it under the terms of the Do What the Fuck You Want
  9. // to Public License, Version 2, as published by the WTFPL Task Force.
  10. // See http://www.wtfpl.net/ for more details.
  11. //
  12. #pragma once
  13. //
  14. // The Unit test framework
  15. // -----------------------
  16. //
  17. #include <iostream>
  18. #include <iomanip>
  19. #include <sstream>
  20. #include <cstdio>
  21. #if defined _XBOX
  22. # include <xtl.h>
  23. # undef near /* Fuck Microsoft */
  24. # undef far /* Fuck Microsoft again */
  25. #elif defined _WIN32
  26. # define WIN32_LEAN_AND_MEAN
  27. # include <windows.h>
  28. # undef near /* Fuck Microsoft */
  29. # undef far /* Fuck Microsoft again */
  30. #endif
  31. namespace lol
  32. {
  33. /*
  34. * This is the base class for all fixtures. It keeps track of all
  35. * fixtures registered through the lolunit_declare_fixture macro and puts them
  36. * in a linked list.
  37. */
  38. class FixtureBase
  39. {
  40. friend class text_runner;
  41. public:
  42. virtual void setup(void) {};
  43. virtual void teardown(void) {};
  44. protected:
  45. FixtureBase() : m_next(NULL), m_testcases(0), m_failcases(0) {}
  46. virtual ~FixtureBase() {}
  47. static void AddFixture(FixtureBase *fixture)
  48. {
  49. /* Protect against several instances of the same Fixture subclass */
  50. if (fixture->MarkFixture())
  51. return;
  52. FixtureListHelper(fixture);
  53. }
  54. static FixtureBase *FixtureList() { return FixtureListHelper(NULL); }
  55. virtual void RunFixture() = 0;
  56. virtual bool MarkFixture() = 0;
  57. /* Prevent compiler complaints about unreachable code */
  58. static inline bool True() { return true; }
  59. FixtureBase *m_next;
  60. int m_testcases, m_failcases;
  61. int m_asserts, m_failure;
  62. char const *m_fixturename, *m_currentname;
  63. std::stringstream m_errorlog, m_context;
  64. private:
  65. /* The FixtureBase class keeps track of all instanciated children
  66. * fixtures through this method. */
  67. static FixtureBase *FixtureListHelper(FixtureBase *set)
  68. {
  69. static FixtureBase *head = NULL, *tail = NULL;
  70. if (set)
  71. {
  72. if (!head) head = set;
  73. if (tail) tail->m_next = set;
  74. tail = set;
  75. }
  76. return head;
  77. }
  78. };
  79. /*
  80. * This template specialises FixtureBase and provides registration of
  81. * test cases in a linked list through the lolunit_declare_test macro.
  82. */
  83. template<class T> class Fixture : protected FixtureBase
  84. {
  85. public:
  86. typedef T FixtureClass;
  87. struct TestCase
  88. {
  89. void (FixtureClass::* m_fun)();
  90. char const *m_testname;
  91. TestCase *m_next;
  92. protected:
  93. static inline std::string make_msg(std::string const str)
  94. {
  95. return "- " + str + "\n";
  96. }
  97. };
  98. Fixture()
  99. {
  100. AddFixture(this);
  101. }
  102. /* Run all test cases in this fixture. */
  103. virtual void RunFixture()
  104. {
  105. m_errorlog.str("");
  106. m_testcases = 0;
  107. m_failcases = 0;
  108. for (TestCase *c = TestCaseList(); c; c = c->m_next)
  109. {
  110. m_testcases++;
  111. m_asserts = 0;
  112. m_failure = false;
  113. m_currentname = c->m_testname;
  114. m_context.str("");
  115. std::cout << '.';
  116. (static_cast<FixtureClass *>(this)->*c->m_fun)();
  117. if (m_failure) std::cout << 'F';
  118. }
  119. }
  120. /* Mark the current fixture type as already registered and return whether
  121. * it was seen before. */
  122. virtual bool MarkFixture()
  123. {
  124. static bool seen = false;
  125. if (seen)
  126. {
  127. SealFixture();
  128. return true;
  129. }
  130. seen = true;
  131. return false;
  132. }
  133. /* Manage Fixture sealing. Once SealFixture() has been called, we
  134. * will no longer accept TestCase registrations. */
  135. static void SealFixture() { SealFixtureHelper(true); }
  136. static bool IsFixtureSealed() { return SealFixtureHelper(false); }
  137. /* Each Fixture class specialisation keeps track of its instanciated
  138. * test cases. */
  139. static void AddTestCase(TestCase *that, char const *name,
  140. void (FixtureClass::*fun)())
  141. {
  142. if (IsFixtureSealed())
  143. return;
  144. that->m_fun = fun;
  145. that->m_testname = name;
  146. that->m_next = NULL;
  147. TestCaseListHelper(that);
  148. }
  149. static TestCase *TestCaseList() { return TestCaseListHelper(NULL); }
  150. private:
  151. static bool SealFixtureHelper(bool set)
  152. {
  153. static bool sealed = false;
  154. if (set) sealed = true;
  155. return sealed;
  156. }
  157. static TestCase *TestCaseListHelper(TestCase *set)
  158. {
  159. static TestCase *head = NULL, *tail = NULL;
  160. if (set)
  161. {
  162. if (!head) head = set;
  163. if (tail) tail->m_next = set;
  164. tail = set;
  165. }
  166. return head;
  167. }
  168. };
  169. /*
  170. * This simple class runs all automatically registered tests and reports
  171. * on error and success in the standard output.
  172. */
  173. class text_runner
  174. {
  175. public:
  176. bool Run()
  177. {
  178. bool ret = true;
  179. std::stringstream errors("");
  180. int failcases = 0, testcases = 0;
  181. for (FixtureBase *f = FixtureBase::FixtureList(); f; f = f->m_next)
  182. {
  183. f->setup();
  184. f->RunFixture();
  185. f->teardown();
  186. errors << f->m_errorlog.str();
  187. testcases += f->m_testcases;
  188. failcases += f->m_failcases;
  189. }
  190. std::cout << "\n";
  191. std::stringstream summary;
  192. summary << "\n\n";
  193. if (failcases)
  194. {
  195. summary << "!!!FAILURES!!!\n";
  196. summary << "Test Results:\n";
  197. summary << "Run: " << testcases
  198. << " Failures: " << failcases
  199. << " Errors: 0\n"; /* TODO: handle errors */
  200. summary << errors.str();
  201. ret = false;
  202. }
  203. else
  204. {
  205. summary << "OK (" << testcases << " tests)\n";
  206. }
  207. summary << "\n\n";
  208. #if _WIN32
  209. ::OutputDebugStringA(summary.str().c_str());
  210. #else
  211. std::cout << summary.str();
  212. #endif
  213. return ret;
  214. }
  215. };
  216. #define lolunit_assert_generic(msg, cond) \
  217. do { \
  218. m_asserts++; \
  219. if (True() && !(cond)) \
  220. { \
  221. m_errorlog << "\n\n"; \
  222. m_errorlog << ++m_failcases << ") test: " \
  223. << lol_unit_helper_name(this) << "::" << m_currentname \
  224. << " (F) line: " << __LINE__ << " " \
  225. << __FILE__ << "\n"; \
  226. m_errorlog << "assertion failed\n"; \
  227. m_errorlog << "- Expression: " << #cond << "\n"; \
  228. m_errorlog << msg; \
  229. m_failure = true; \
  230. return; \
  231. } \
  232. } while (!True())
  233. #define lolunit_assert_op(op, modifier, opdesc, msg, a, b) \
  234. do { \
  235. m_asserts++; \
  236. if (True() && !modifier((a) op (b))) \
  237. { \
  238. m_errorlog << "\n\n"; \
  239. m_errorlog << ++m_failcases << ") test: " \
  240. << lol_unit_helper_name(this) << "::" << m_currentname \
  241. << " (F) line: " << __LINE__ << " " \
  242. << __FILE__ << "\n"; \
  243. m_errorlog << opdesc << " assertion failed\n"; \
  244. m_errorlog << "- Expected: " << #a << " = " << (a) << "\n"; \
  245. m_errorlog << "- Actual : " << #b << " = " << (b) << "\n"; \
  246. m_errorlog << msg; \
  247. m_errorlog << m_context.str(); \
  248. m_failure = true; \
  249. return; \
  250. } \
  251. } while (!True())
  252. #define lolunit_assert_doubles_equal_generic(msg, a, b, t) \
  253. do { \
  254. m_asserts++; \
  255. using std::fabs; \
  256. if (True() && fabs(double(a) - double(b)) > fabs(double(t))) \
  257. { \
  258. m_errorlog << "\n\n"; \
  259. m_errorlog << ++m_failcases << ") test: " \
  260. << lol_unit_helper_name(this) << "::" << m_currentname \
  261. << " (F) line: " << __LINE__ << " " \
  262. << __FILE__ << "\n"; \
  263. m_errorlog << "double equality assertion failed\n"; \
  264. std::streamsize old_prec = m_errorlog.precision(); \
  265. m_errorlog << std::setprecision(16); \
  266. m_errorlog << "- Expected: " << #a << " = " << (a) << "\n"; \
  267. m_errorlog << "- Actual : " << #b << " = " << (b) << "\n"; \
  268. m_errorlog << "- Delta : " << (t) << "\n"; \
  269. m_errorlog << std::setprecision(old_prec); \
  270. m_errorlog << msg; \
  271. m_errorlog << m_context.str(); \
  272. m_failure = true; \
  273. return; \
  274. } \
  275. } while (!True())
  276. /*
  277. * Public helper macros
  278. */
  279. #define lolunit_declare_fixture(N) \
  280. class N; \
  281. /* This pattern allows us to statically create a Fixture instance \
  282. * before its exact implementation was defined. */ \
  283. template<typename T> struct lol_unit_helper_fixture_##N \
  284. { \
  285. lol_unit_helper_fixture_##N() { p = new T(); } \
  286. ~lol_unit_helper_fixture_##N() { delete p; } \
  287. T *p; \
  288. }; \
  289. lol_unit_helper_fixture_##N<N> lol_unit_helper_fixture_##N##_instance; \
  290. /* Allow to retrieve the class name without using RTTI and without \
  291. * knowing the type of "this". */ \
  292. static inline char const *lol_unit_helper_name(N *p) \
  293. { \
  294. (void)p; \
  295. return #N; \
  296. } \
  297. /* Now the user can define the implementation */ \
  298. class N : public lol::Fixture<N>
  299. #define lolunit_declare_test(N) \
  300. /* For each test in the fixture, we create an object that will \
  301. * automatically register the test method in a list global to the \
  302. * specialised fixture. */ \
  303. class lol_unit_helper_test_##N : public TestCase \
  304. { \
  305. public: \
  306. lol_unit_helper_test_##N() \
  307. { \
  308. (void)lol_unit_helper_name(nullptr); \
  309. AddTestCase(this, #N, \
  310. (void (FixtureClass::*)()) &FixtureClass::N); \
  311. } \
  312. }; \
  313. lol_unit_helper_test_##N lol_unit_helper_test_instance_##N; \
  314. void N()
  315. /*
  316. * Provide context for error messages
  317. */
  318. #define lolunit_set_context(n) \
  319. do { \
  320. m_context.str(""); \
  321. m_context << "- Context : " << #n << " = " << (n) << "\n"; \
  322. } while (!True())
  323. #define lolunit_unset_context(n) \
  324. m_context.str("")
  325. /*
  326. * Public assert macros
  327. */
  328. #define lolunit_fail(msg) \
  329. do { \
  330. m_asserts++; \
  331. m_errorlog << "\n\n"; \
  332. m_errorlog << ++m_failcases << ") test: " \
  333. << lol_unit_helper_name(this) << "::" << m_currentname \
  334. << " (F) line: " << __LINE__ << " " \
  335. << __FILE__ << "\n"; \
  336. m_errorlog << "forced failure\n"; \
  337. m_errorlog << make_msg(msg); \
  338. m_errorlog << m_context.str(); \
  339. m_failure = true; \
  340. return; \
  341. } while (!True())
  342. #define lolunit_assert(cond) \
  343. lolunit_assert_generic("", cond)
  344. #define lolunit_assert_message(m, cond) \
  345. lolunit_assert_generic(make_msg(m), cond)
  346. #define lolunit_assert_equal(a, b) \
  347. lolunit_assert_op(==, (bool), "equality", "", a, b)
  348. #define lolunit_assert_equal_message(m, a, b) \
  349. lolunit_assert_op(==, (bool), "equality", make_msg(m), a, b)
  350. #define lolunit_assert_different(a, b) \
  351. lolunit_assert_op(!=, (bool), "inequality", "", a, b)
  352. #define lolunit_assert_different_message(m, a, b) \
  353. lolunit_assert_op(!=, (bool), "inequality", make_msg(m), a, b)
  354. #define lolunit_assert_less(a, b) \
  355. lolunit_assert_op(<, (bool), "less than", "", a, b)
  356. #define lolunit_assert_less_message(m, a, b) \
  357. lolunit_assert_op(<, (bool), "less than", make_msg(m), a, b)
  358. #define lolunit_assert_lequal(a, b) \
  359. lolunit_assert_op(<=, (bool), "less than or equal", "", a, b)
  360. #define lolunit_assert_lequal_message(m, a, b) \
  361. lolunit_assert_op(<=, (bool), "less than or equal", make_msg(m), a, b)
  362. #define lolunit_assert_greater(a, b) \
  363. lolunit_assert_op(>, (bool), "greater than", "", a, b)
  364. #define lolunit_assert_greater_message(m, a, b) \
  365. lolunit_assert_op(>, (bool), "greater than", make_msg(m), a, b)
  366. #define lolunit_assert_gequal(a, b) \
  367. lolunit_assert_op(>=, (bool), "greater than or equal", "", a, b)
  368. #define lolunit_assert_gequal_message(m, a, b) \
  369. lolunit_assert_op(>=, (bool), "greater than or equal", make_msg(m), a, b)
  370. #define lolunit_refute_equal(a, b) \
  371. lolunit_assert_op(==, !, "not equality", "", a, b)
  372. #define lolunit_refute_equal_message(m, a, b) \
  373. lolunit_assert_op(==, !, "not equality", make_msg(m), a, b)
  374. #define lolunit_refute_different(a, b) \
  375. lolunit_assert_op(!=, !, "not inequality", "", a, b)
  376. #define lolunit_refute_different_message(m, a, b) \
  377. lolunit_assert_op(!=, !, "not inequality", make_msg(m), a, b)
  378. #define lolunit_refute_less(a, b) \
  379. lolunit_assert_op(<, !, "not less than", "", a, b)
  380. #define lolunit_refute_less_message(m, a, b) \
  381. lolunit_assert_op(<, !, "not less than", make_msg(m), a, b)
  382. #define lolunit_refute_lequal(a, b) \
  383. lolunit_assert_op(<=, !, "not less than or equal", "", a, b)
  384. #define lolunit_refute_lequal_message(m, a, b) \
  385. lolunit_assert_op(<=, !, "not less than or equal", make_msg(m), a, b)
  386. #define lolunit_refute_greater(a, b) \
  387. lolunit_assert_op(>, !, "not greater than", "", a, b)
  388. #define lolunit_refute_greater_message(m, a, b) \
  389. lolunit_assert_op(>, !, "not greater than", make_msg(m), a, b)
  390. #define lolunit_refute_gequal(a, b) \
  391. lolunit_assert_op(>=, !, "not greater than or equal", "", a, b)
  392. #define lolunit_refute_gequal_message(m, a, b) \
  393. lolunit_assert_op(>=, !, "not greater than or equal", make_msg(m), a, b)
  394. #define lolunit_assert_doubles_equal(a, b, t) \
  395. lolunit_assert_doubles_equal_generic("", a, b, t)
  396. #define lolunit_assert_doubles_equal_message(msg, a, b, t) \
  397. lolunit_assert_doubles_equal_generic(make_msg(msg), a, b, t)
  398. } /* namespace lol */