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.

443 lines
12KB

  1. // ---
  2. //
  3. // $Id: htmloutput.cpp,v 1.7 2008/07/15 20:33:31 hartwork Exp $
  4. //
  5. // CppTest - A C++ Unit Testing Framework
  6. // Copyright (c) 2003 Niklas Lundell
  7. //
  8. // ---
  9. //
  10. // This library is free software; you can redistribute it and/or
  11. // modify it under the terms of the GNU Lesser General Public
  12. // License as published by the Free Software Foundation; either
  13. // version 2 of the License, or (at your option) any later version.
  14. //
  15. // This library is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. // Lesser General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU Lesser General Public
  21. // License along with this library; if not, write to the
  22. // Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  23. // Boston, MA 02111-1307, USA.
  24. //
  25. // ---
  26. #include "cpptest-htmloutput.h"
  27. #include "utils.h"
  28. namespace Test
  29. {
  30. namespace
  31. {
  32. void
  33. strreplace(std::string &value, char search, const std::string &replace)
  34. {
  35. std::string::size_type idx = 0;
  36. while((idx = value.find(search, idx)) != std::string::npos)
  37. {
  38. value.replace(idx, 1, replace);
  39. idx += replace.size();
  40. }
  41. }
  42. std::string
  43. escape(std::string value)
  44. {
  45. strreplace(value, '&', "&");
  46. strreplace(value, '<', "&lt;");
  47. strreplace(value, '>', "&gt;");
  48. strreplace(value, '"', "&quot;");
  49. strreplace(value, '\'', "&#39;");
  50. return value;
  51. }
  52. void
  53. header(std::ostream& os, std::string name)
  54. {
  55. if (!name.empty())
  56. name += " ";
  57. name = escape(name);
  58. os <<
  59. "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
  60. "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
  61. "<head>\n"
  62. " <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n"
  63. " <meta name=\"generator\" content=\"CppTest - http://cpptest.sourceforge.net\" />\n"
  64. " \n"
  65. " <title>" << name << "Unit Tests Results</title>\n"
  66. " \n"
  67. " <style type=\"text/css\" media=\"screen\">\n"
  68. " <!--\n"
  69. " hr {\n"
  70. " width: 100%;\n"
  71. " border-width: 0px;\n"
  72. " height: 1px;\n"
  73. " color: #cccccc;\n"
  74. " background-color: #cccccc;\n"
  75. " padding: 0px;\n"
  76. " }\n"
  77. " \n"
  78. " table {\n"
  79. " width:100%;\n"
  80. " border-collapse:separate;\n"
  81. " border-spacing: 2px;\n"
  82. " border:0px;\n"
  83. " }\n"
  84. " tr {\n"
  85. " margin:0px;\n"
  86. " padding:0px;\n"
  87. " }\n"
  88. " td {\n"
  89. " margin:0px;\n"
  90. " padding:1px;\n"
  91. " }\n"
  92. " .table_summary {\n"
  93. " }\n"
  94. " .table_suites {\n"
  95. " }\n"
  96. " .table_suite {\n"
  97. " }\n"
  98. " .table_result {\n"
  99. " margin: 0px 0px 1em 0px;\n"
  100. " }\n"
  101. " .tablecell_title {\n"
  102. " background-color: #a5cef7;\n"
  103. " font-weight: bold;\n"
  104. " }\n"
  105. " \n"
  106. " .tablecell_success {\n"
  107. " background-color: #efefe7;\n"
  108. " }\n"
  109. " \n"
  110. " .tablecell_error {\n"
  111. " color: #ff0808;\n"
  112. " background-color: #efefe7;\n"
  113. " font-weight: bold;\n"
  114. " }\n"
  115. " p.spaced {\n"
  116. " margin: 0px;\n"
  117. " padding: 1em 0px 2em 0px;\n"
  118. " }\n"
  119. " p.unspaced {\n"
  120. " margin: 0px;\n"
  121. " padding: 0px 0px 2em 0px;\n"
  122. " }\n"
  123. " -->\n"
  124. " </style>\n"
  125. "</head>\n"
  126. "\n"
  127. "<body>\n"
  128. "\n"
  129. "<h1><a name=\"top\"></a>" << name << "Unit Tests Results</h1>\n"
  130. "\n"
  131. "<div style=\"text-align:right\">\n"
  132. "Designed by <a href=\"http://cpptest.sourceforge.net\">CppTest</a>\n"
  133. "</div>\n"
  134. "<hr />\n"
  135. "\n";
  136. }
  137. void
  138. footer(std::ostream& os)
  139. {
  140. os <<
  141. "\n"
  142. "<p>\n"
  143. " <a href=\"http://validator.w3.org/#validate-by-upload\">\n"
  144. " Valid XHTML 1.0 Strict\n"
  145. " </a>\n"
  146. "</p>\n"
  147. "</body>\n</html>\n";
  148. }
  149. void
  150. back_ref(std::ostream& os, const std::string& ref, bool prepend_newline = true)
  151. {
  152. os << "<p class=\"" << (prepend_newline ? "spaced" : "unspaced") << "\"><a href=\"#" << ref
  153. << "\">Back to " << escape(ref) << "</a>\n</p>\n";
  154. }
  155. void
  156. sub_title(std::ostream& os, const std::string& title, int size)
  157. {
  158. std::ostringstream h;
  159. h << "h" << size;
  160. os << "<" << h.str() << ">" << escape(title) << "</" << h.str() << ">\n";
  161. }
  162. void
  163. sub_title(std::ostream& os, const std::string& title, int size, const std::string& mark)
  164. {
  165. std::ostringstream h;
  166. h << "h" << size;
  167. os << "<" << h.str() << "><a name=\"" << mark << "\"></a>" << escape(title) << "</" << h.str() << ">\n";
  168. }
  169. enum ClassTableType { TableClass_Summary, TableClass_Suites, TableClass_Suite, TableClass_Result };
  170. void
  171. table_header(std::ostream& os, ClassTableType type, const std::string &summary = "")
  172. {
  173. static const char* class_tabletypes[] = { "summary", "suites", "suite", "result" };
  174. os << "<table summary=\"" << escape(summary) << "\" class=\"table_" << class_tabletypes[type] << "\">\n";
  175. }
  176. void
  177. table_footer(std::ostream& os)
  178. {
  179. os << "</table>\n";
  180. }
  181. void
  182. table_tr_header(std::ostream& os)
  183. {
  184. os << " <tr>\n";
  185. }
  186. void
  187. table_tr_footer(std::ostream& os)
  188. {
  189. os << " </tr>\n";
  190. }
  191. enum ClassType { Title, Success, Error };
  192. void
  193. table_entry(std::ostream& os, ClassType type, const std::string& s,
  194. int width = 0, const std::string& link = "")
  195. {
  196. static const char* class_types[] = { "title", "success", "error" };
  197. os << " <td";
  198. if (width)
  199. os << " style=\"width:" << width << "%\"";
  200. if (!link.empty())
  201. os << " class=\"tablecell_" << class_types[type] << "\"><a href=\"#" << link << "\">" << escape(s) << "</a>";
  202. else
  203. os << " class=\"tablecell_" << class_types[type] << "\">" << escape(s);
  204. os << "</td>\n";
  205. }
  206. } // anonymous namespace
  207. // Test suite table
  208. //
  209. struct HtmlOutput::SuiteRow
  210. {
  211. std::ostream& _os;
  212. SuiteRow(std::ostream& os) : _os(os) {}
  213. void operator()(const SuiteInfo& si)
  214. {
  215. ClassType type(si._errors > 0 ? Error : Success);
  216. std::ostringstream ss;
  217. table_tr_header(_os);
  218. table_entry(_os, type, si._name, 0, si._name);
  219. ss.str(""), ss << si._tests.size();
  220. table_entry(_os, type, ss.str(), 10);
  221. ss.str(""), ss << si._errors;
  222. table_entry(_os, type, ss.str(), 10);
  223. ss.str(""), ss << correct(si._tests.size(), si._errors) << "%";
  224. table_entry(_os, type, ss.str(), 10);
  225. ss.str(""), ss << si._time;
  226. table_entry(_os, type, ss.str(), 10);
  227. table_tr_footer(_os);
  228. }
  229. };
  230. // Individual tests tables, tests
  231. //
  232. struct HtmlOutput::TestRow
  233. {
  234. bool _incl_ok_tests;
  235. std::ostream& _os;
  236. TestRow(std::ostream& os, bool incl_ok_tests)
  237. : _incl_ok_tests(incl_ok_tests), _os(os) {}
  238. void operator()(const TestInfo& ti)
  239. {
  240. if (!ti._success || _incl_ok_tests)
  241. {
  242. std::string link = ti._success ? std::string(""):
  243. ti._sources.front().suite() + "_" + ti._name;
  244. ClassType type(ti._success ? Success : Error);
  245. std::ostringstream ss;
  246. table_tr_header(_os);
  247. table_entry(_os, type, ti._name, 0, link);
  248. ss.str(""), ss << ti._sources.size();
  249. table_entry(_os, type, ss.str());
  250. table_entry(_os, type, ti._success ? "true" : "false");
  251. ss.str(""), ss << ti._time;
  252. table_entry(_os, type, ss.str());
  253. table_tr_footer(_os);
  254. }
  255. }
  256. };
  257. // Individual tests tables, header
  258. //
  259. struct HtmlOutput::TestSuiteRow
  260. {
  261. bool _incl_ok_tests;
  262. std::ostream& _os;
  263. TestSuiteRow(std::ostream& os, bool incl_ok_tests)
  264. : _incl_ok_tests(incl_ok_tests), _os(os) {}
  265. void operator()(const SuiteInfo& si)
  266. {
  267. std::ostringstream ss;
  268. sub_title(_os, "Suite: " + si._name, 3, si._name);
  269. table_header(_os, TableClass_Suite, "Details for suite " + si._name);
  270. table_tr_header(_os);
  271. table_entry(_os, Title, "Name");
  272. table_entry(_os, Title, "Errors", 10);
  273. table_entry(_os, Title, "Success", 10);
  274. table_entry(_os, Title, "Time (s)", 10);
  275. table_tr_footer(_os);
  276. std::for_each(si._tests.begin(), si._tests.end(),
  277. TestRow(_os, _incl_ok_tests));
  278. table_footer(_os);
  279. back_ref(_os, "top");
  280. }
  281. };
  282. // Individual tests result tables
  283. //
  284. struct HtmlOutput::TestResult
  285. {
  286. std::ostream& _os;
  287. TestResult(std::ostream& os) : _os(os) {}
  288. void operator()(const Source& s)
  289. {
  290. const int TitleSize = 15;
  291. std::ostringstream ss;
  292. table_header(_os, TableClass_Result, "Test Failure");
  293. table_tr_header(_os);
  294. table_entry(_os, Title, "Test", TitleSize);
  295. table_entry(_os, Success, s.suite() + "::" + s.test());
  296. table_tr_footer(_os);
  297. table_tr_header(_os);
  298. table_entry(_os, Title, "File", TitleSize);
  299. ss << s.file() << ":" << s.line();
  300. table_entry(_os, Success, ss.str());
  301. table_tr_footer(_os);
  302. table_tr_header(_os);
  303. table_entry(_os, Title, "Message", TitleSize);
  304. table_entry(_os, Success, s.message());
  305. table_tr_footer(_os);
  306. table_footer(_os);
  307. }
  308. };
  309. // All tests result tables
  310. //
  311. struct HtmlOutput::TestResultAll
  312. {
  313. std::ostream& _os;
  314. TestResultAll(std::ostream& os) : _os(os) {}
  315. void operator()(const TestInfo& ti)
  316. {
  317. if (!ti._success)
  318. {
  319. const std::string& suite = ti._sources.front().suite();
  320. sub_title(_os, suite + "::" + ti._name, 3, suite + "_" + ti._name);
  321. std::for_each(ti._sources.begin(), ti._sources.end(), TestResult(_os));
  322. back_ref(_os, suite, false);
  323. }
  324. }
  325. };
  326. // Individual tests result tables, iterator
  327. //
  328. struct HtmlOutput::SuiteTestResult
  329. {
  330. std::ostream& _os;
  331. SuiteTestResult(std::ostream& os) : _os(os) {}
  332. void operator()(const SuiteInfo& si)
  333. {
  334. std::for_each(si._tests.begin(), si._tests.end(), TestResultAll(_os));
  335. }
  336. };
  337. /// Generates the HTML table. This function should only be called after
  338. /// run(), when all tests have been executed.
  339. ///
  340. /// \param os Output stream.
  341. /// \param incl_ok_tests Set if successful tests should be shown;
  342. /// false otherwise.
  343. /// \param name Name of generated report.
  344. ///
  345. void
  346. HtmlOutput::generate(std::ostream& os, bool incl_ok_tests, const std::string& name)
  347. {
  348. ClassType type(_total_errors > 0 ? Error : Success);
  349. std::ostringstream ss;
  350. header(os, name);
  351. // Table: Summary
  352. //
  353. sub_title(os, "Summary", 2);
  354. table_header(os, TableClass_Summary, "Summary of test results");
  355. table_tr_header(os);
  356. table_entry(os, Title, "Tests", 30);
  357. table_entry(os, Title, "Errors", 30);
  358. table_entry(os, Title, "Success", 30);
  359. table_entry(os, Title, "Time (s)", 10);
  360. table_tr_footer(os);
  361. table_tr_header(os);
  362. ss.str(""), ss << _total_tests;
  363. table_entry(os, type, ss.str(), 30);
  364. ss.str(""), ss << _total_errors;
  365. table_entry(os, type, ss.str(), 30);
  366. ss.str(""), ss << correct(_total_tests, _total_errors) << "%";
  367. table_entry(os, type, ss.str(), 30);
  368. ss.str(""), ss << _total_time;
  369. table_entry(os, type, ss.str(), 10);
  370. table_tr_footer(os);
  371. table_footer(os);
  372. os << "<hr />\n\n";
  373. // Table: Test suites
  374. //
  375. sub_title(os, "Test suites", 2);
  376. table_header(os, TableClass_Suites, "Test Suites");
  377. table_tr_header(os);
  378. table_entry(os, Title, "Name");
  379. table_entry(os, Title, "Tests", 10);
  380. table_entry(os, Title, "Errors", 10);
  381. table_entry(os, Title, "Success", 10);
  382. table_entry(os, Title, "Time (s)", 10);
  383. table_tr_footer(os);
  384. std::for_each(_suites.begin(), _suites.end(), SuiteRow(os));
  385. table_footer(os);
  386. os << "<hr />\n\n";
  387. // Individual tests tables
  388. //
  389. std::for_each(_suites.begin(), _suites.end(), TestSuiteRow(os, incl_ok_tests));
  390. os << "<hr />\n\n";
  391. // Individual tests result tables
  392. //
  393. if(_total_errors != 0)
  394. {
  395. sub_title(os, "Test results", 2);
  396. std::for_each(_suites.begin(), _suites.end(), SuiteTestResult(os));
  397. os << "<hr />\n\n";
  398. }
  399. // EOF
  400. //
  401. footer(os);
  402. }
  403. } // namespace Test