dfcd858ef7fdbc5078b64c5873cc819ac96a866b
[feisty_meow.git] / nucleus / library / unit_test / unit_base.cpp
1 /*
2 *  Name   : unit test tools
3 *  Author : Chris Koeritz
4 **
5 * Copyright (c) 2009-$now By Author.  This program is free software; you can  *
6 * redistribute it and/or modify it under the terms of the GNU General Public  *
7 * License as published by the Free Software Foundation; either version 2 of   *
8 * the License or (at your option) any later version.  This is online at:      *
9 *     http://www.fsf.org/copyleft/gpl.html                                    *
10 * Please send any updates to: fred@gruntose.com                               *
11 */
12
13 #include "unit_base.h"
14
15 #include <application/application_shell.h>
16 #include <basis/astring.h>
17 #include <basis/functions.h>
18 #include <configuration/application_configuration.h>
19 #include <loggers/program_wide_logger.h>
20 #include <filesystem/byte_filer.h>
21 #include <filesystem/filename.h>
22 #include <structures/string_table.h>
23 #include <textual/xml_generator.h>
24
25 using namespace application;
26 using namespace basis;
27 using namespace configuration;
28 using namespace filesystem;
29 using namespace loggers;
30 using namespace structures;
31 using namespace textual;
32
33 #define BASE_LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s)
34
35 namespace unit_test {
36
37 const int EXPECTED_MAXIMUM_TESTS = 10008;  //!< maximum number of tests expected.
38
39 const char *name_for_bools(bool bv)
40 { if (bv) return "true"; else return "false"; }
41
42 unit_base::unit_base()
43 : c_lock(),
44   c_total_tests(0),
45   c_passed_tests(0),
46   c_successful(EXPECTED_MAXIMUM_TESTS),
47   c_failed(EXPECTED_MAXIMUM_TESTS)
48 {
49 }
50
51 unit_base::~unit_base() {}
52
53 int unit_base::total_tests() const { return c_total_tests; }
54
55 int unit_base::passed_tests() const { return c_passed_tests; }
56
57 int unit_base::failed_tests() const
58 { return c_total_tests - c_passed_tests; }
59
60 void unit_base::count_successful_test(const basis::astring &class_name, const basis::astring &test_name)
61 {
62   auto_synchronizer synch(c_lock);
63   outcome ret = c_successful.add(class_name + " -- " + test_name, "");
64   if (ret == common::IS_NEW) {
65     c_total_tests++;
66     c_passed_tests++;
67   }
68 }
69
70 void unit_base::record_pass(const basis::astring &class_name, const astring &test_name,
71     const astring &diag)
72 {
73   auto_synchronizer synch(c_lock);
74   count_successful_test(class_name, test_name);
75 //hmmm: kind of lame bailout on printing this.
76 //      it gets very very wordy if it's left in.
77 #ifdef DEBUG
78   astring message = astring("OKAY: ") + class_name + " in test [" + test_name + "]";
79   BASE_LOG(message);
80 #endif
81 }
82
83 void unit_base::count_failed_test(const basis::astring &class_name, const basis::astring &test_name,
84     const basis::astring &diag)
85 {
86   auto_synchronizer synch(c_lock);
87   outcome ret = c_failed.add(class_name + " -- " + test_name, diag);
88   if (ret == common::IS_NEW) {
89     c_total_tests++;
90   }
91 }
92
93 void unit_base::record_fail(const basis::astring &class_name, const astring &test_name, const astring &diag)
94 {
95   count_failed_test(class_name, test_name, diag);
96   astring message = astring("\nFAIL: ") + class_name + " in test [" + test_name + "]\n" + diag + "\n";
97   BASE_LOG(message);
98 }
99
100 void unit_base::record_successful_assertion(const basis::astring &class_name, const astring &test_name,
101     const astring &assertion_name)
102 {
103   record_pass(class_name, test_name,
104       astring("no problem with: ") + assertion_name);
105 }
106
107 void unit_base::record_failed_object_compare(const hoople_standard &a, const hoople_standard &b,
108     const basis::astring &class_name, const astring &test_name, const astring &assertion_name)
109 {
110   astring a_state, b_state;
111   a.text_form(a_state);
112   b.text_form(b_state);
113   record_fail(class_name, test_name,
114       astring("Error in assertion ") + assertion_name + ":\n"
115       + "==============\n"
116       + a_state + "\n"
117       + "=== versus ===\n"
118       + b_state + "\n"
119       + "==============");
120 }
121
122 void unit_base::record_failed_int_compare(int a, int b,
123     const basis::astring &class_name, const astring &test_name, const astring &assertion_name)
124 {
125   record_fail(class_name, test_name,
126       astring("Error in assertion ") + assertion_name
127       + a_sprintf(": inappropriate values: %d & %d", a, b));
128 }
129
130 void unit_base::record_failed_double_compare(double a, double b,
131     const basis::astring &class_name, const astring &test_name, const astring &assertion_name)
132 {
133   record_fail(class_name, test_name,
134       astring("Error in assertion ") + assertion_name
135       + a_sprintf(": inappropriate values: %f & %f", a, b));
136 }
137
138 void unit_base::record_failed_pointer_compare(const void *a, const void *b,
139     const basis::astring &class_name, const astring &test_name, const astring &assertion_name)
140 {
141   record_fail(class_name, test_name,
142       astring("Error in assertion ") + assertion_name
143       + a_sprintf(": inappropriate values: %p & %p", a, b));
144 }
145
146 void unit_base::record_failed_tf_assertion(bool result,
147     bool expected_result, const basis::astring &class_name, const astring &test_name,
148     const astring &assertion_name)
149 {
150   record_fail(class_name, test_name, astring("Error in assertion ") + assertion_name
151       + ": expected " + name_for_bools(expected_result)
152       + " but found " + name_for_bools(result));
153 }
154
155 void unit_base::assert_equal(const hoople_standard &a, const hoople_standard &b,
156     const basis::astring &class_name, const astring &test_name, const astring &diag)
157 {
158   FUNCDEF("assert_equal");
159   bool are_equal = (a == b);
160   if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
161   else record_failed_object_compare(a, b, class_name, test_name, func);
162 }
163
164 void unit_base::assert_not_equal(const hoople_standard &a, const hoople_standard &b,
165     const basis::astring &class_name, const astring &test_name, const astring &diag)
166 {  
167   FUNCDEF("assert_not_equal");
168   bool are_equal = (a == b);
169   if (!are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
170   else record_failed_object_compare(a, b, class_name, test_name, func);
171 }
172
173 const char *byte_array_phrase = "byte_array[%d]";  // for printing a text form of byte_array.
174
175 void unit_base::assert_equal(const basis::byte_array &a, const basis::byte_array &b,
176     const basis::astring &class_name, const basis::astring &test_name, const astring &diag)
177 {
178   FUNCDEF("assert_equal");
179   bool are_equal = (a == b);
180   if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
181   else {
182     astring a_s = a_sprintf(byte_array_phrase, a.length());
183     astring b_s = a_sprintf(byte_array_phrase, b.length());
184     record_failed_object_compare(a_s, b_s, class_name, test_name, func);
185   }
186 }
187
188 void unit_base::assert_not_equal(const basis::byte_array &a, const basis::byte_array &b,
189     const basis::astring &class_name, const basis::astring &test_name, const astring &diag)
190 {  
191   FUNCDEF("assert_not_equal");
192   bool are_equal = (a == b);
193   if (!are_equal) record_successful_assertion(class_name, test_name, func);
194   else {
195     astring a_s = a_sprintf(byte_array_phrase, a.length());
196     astring b_s = a_sprintf(byte_array_phrase, b.length());
197     record_failed_object_compare(a_s, b_s, class_name, test_name, func);
198   }
199 }
200
201 void unit_base::assert_equal(int a, int b, const basis::astring &class_name, const astring &test_name, const astring &diag)
202 {
203   FUNCDEF("assert_equal integer");
204   bool are_equal = a == b;
205   if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
206   else record_failed_int_compare(a, b, class_name, test_name, astring(func) + ": " + diag);
207 }
208
209 void unit_base::assert_not_equal(int a, int b, const basis::astring &class_name, const astring &test_name, const astring &diag)
210 {
211   FUNCDEF("assert_not_equal integer");
212   bool are_inequal = a != b;
213   if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
214   else record_failed_int_compare(a, b, class_name, test_name, astring(func) + ": " + diag);
215 }
216
217 void unit_base::assert_equal(double a, double b, const basis::astring &class_name, const astring &test_name, const astring &diag)
218 {
219   FUNCDEF("assert_equal double");
220   bool are_equal = a == b;
221   if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
222   else record_failed_double_compare(a, b, class_name, test_name, astring(func) + ": " + diag);
223 }
224
225 void unit_base::assert_not_equal(double a, double b, const basis::astring &class_name, const astring &test_name, const astring &diag)
226 {
227   FUNCDEF("assert_not_equal double");
228   bool are_inequal = a != b;
229   if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
230   else record_failed_double_compare(a, b, class_name, test_name, astring(func) + ": " + diag);
231 }
232
233
234 void unit_base::assert_equal(const void *a, const void *b,
235     const basis::astring &class_name, const astring &test_name, const astring &diag)
236 {
237   FUNCDEF("assert_equal void pointer");
238   bool are_equal = a == b;
239   if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
240   else record_failed_pointer_compare(a, b, class_name, test_name, astring(func) + ": " + diag);
241 }
242
243 void unit_base::assert_not_equal(const void *a, const void *b,
244     const basis::astring &class_name, const astring &test_name, const astring &diag)
245 {
246   FUNCDEF("assert_not_equal void pointer");
247   bool are_inequal = a != b;
248   if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
249   else record_failed_pointer_compare(a, b, class_name, test_name, astring(func) + ": " + diag);
250 }
251
252 void unit_base::assert_true(bool result, const basis::astring &class_name, const astring &test_name, const astring &diag)
253 {
254   FUNCDEF("assert_true");
255   if (result) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
256   else record_failed_tf_assertion(result, true, class_name, test_name, astring(func) + ": " + diag);
257 }
258
259 void unit_base::assert_false(bool result, const basis::astring &class_name, const astring &test_name, const astring &diag)
260 {
261   FUNCDEF("assert_false");
262   if (!result) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag);
263   else record_failed_tf_assertion(result, false, class_name, test_name, astring(func) + ": " + diag);
264 }
265
266 int unit_base::final_report()
267 {
268   auto_synchronizer synch(c_lock);
269   int to_return = 0;  // return success until we know otherwise.
270
271   astring keyword = "FAILURE";  // but be pessimistic about overall result at first..?
272
273
274 BASE_LOG(a_sprintf("total tests %d passed tests %d", c_total_tests, c_passed_tests));
275
276   // check whether we really did succeed or not.
277   if (c_total_tests == c_passed_tests) keyword = "SUCCESS";  // success!
278   else to_return = 12;  // a failure return.
279
280   if (!c_total_tests) keyword = "LAMENESS (no tests!)";  // boring!
281
282 //  astring message = keyword + " for "
283 //    + application_configuration::application_name()
284 //    + a_sprintf(": %d of %d atomic tests passed.",
285 //      c_passed_tests, c_total_tests);
286 //  BASE_LOG(message);
287
288   astring message = keyword + " for "
289     + filename(application_configuration::application_name()).basename().raw()
290     + a_sprintf(": %d of %d unit tests passed.",
291       c_successful.symbols(), c_successful.symbols() + c_failed.symbols());
292   BASE_LOG(message);
293
294   // send an xml file out for the build engine to analyze.
295   write_cppunit_xml();
296
297   return to_return;
298 }
299
300 void unit_base::write_cppunit_xml()
301 {
302   auto_synchronizer synch(c_lock);
303   astring logs_dir = environment::get("LOGS_DIR");
304   if (logs_dir == astring::empty_string()) logs_dir = "logs";  // uhhh.
305   astring outfile = logs_dir + "/"
306       + filename(application_configuration::application_name()).basename().raw()
307       + ".xml";
308
309 //BASE_LOG(astring("outfile is ") + outfile);
310
311   int id = 1;
312
313   xml_generator report;
314   string_table attribs;
315
316   // we are emulating a cppunit xml output.
317
318   report.open_tag("TestRun");
319
320   report.open_tag("FailedTests");
321   for (int i = 0; i < c_failed.symbols(); i++) {
322     attribs.reset();
323     attribs.add("id", a_sprintf("%d", id++));
324     report.open_tag("FailedTest", attribs);
325     attribs.reset();
326 //hmmm: does open_tag eat the attribs?  we could stop worrying about resetting.
327     report.open_tag("Name");
328     report.add_content(c_failed.name(i));
329     report.close_tag("Name");
330
331     report.open_tag("FailureType", attribs);
332     report.add_content("Assertion");
333     report.close_tag("FailureType");
334
335     report.open_tag("Location", attribs);
336
337     report.open_tag("File", attribs);
338     report.add_content(application_configuration::application_name());
339     report.close_tag("File");
340
341     report.open_tag("Line", attribs);
342     report.add_content("0");
343     report.close_tag("Line");
344
345     report.close_tag("Location");
346
347     report.open_tag("Message");
348     report.add_content(c_failed[i]);
349     report.close_tag("Message");
350
351     report.close_tag("FailedTest");
352   }
353   report.close_tag("FailedTests");
354
355   report.open_tag("SuccessfulTests");
356   for (int i = 0; i < c_successful.symbols(); i++) {
357     attribs.reset();
358     attribs.add("id", a_sprintf("%d", id++));
359     attribs.reset();
360     report.open_tag("Test", attribs);
361     report.open_tag("Name");
362     report.add_content(c_successful.name(i));
363     report.close_tag("Name");
364     report.close_tag("Test");
365   }
366   report.close_tag("SuccessfulTests");
367
368   report.open_tag("Statistics");
369   report.open_tag("Tests");
370   report.add_content(a_sprintf("%d", c_failed.symbols() + c_successful.symbols()));
371   report.close_tag("Tests");
372
373   report.open_tag("FailuresTotal");
374   report.add_content(a_sprintf("%d", c_failed.symbols()));
375   report.close_tag("FailuresTotal");
376
377   report.open_tag("Errors");
378   report.add_content("0");
379   report.close_tag("Errors");
380
381   report.open_tag("Failures");
382   report.add_content(a_sprintf("%d", c_failed.symbols()));
383   report.close_tag("Failures");
384
385   report.close_tag("Statistics");
386
387   report.close_tag("TestRun");
388
389   astring text_report = report.generate();
390 //  BASE_LOG(astring("got report\n") + text_report);
391
392   byte_filer xml_out(outfile, "wb");
393   xml_out.write(text_report);
394   xml_out.close();
395 }
396
397 } //namespace.
398