first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / 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   // check whether we really did succeed or not.
274   if (c_total_tests == c_passed_tests) keyword = "SUCCESS";  // success!
275   else to_return = 12;  // a failure return.
276
277   if (!c_total_tests) keyword = "LAMENESS (no tests!)";  // boring!
278
279 //  astring message = keyword + " for "
280 //    + application_configuration::application_name()
281 //    + a_sprintf(": %d of %d atomic tests passed.",
282 //      c_passed_tests, c_total_tests);
283 //  BASE_LOG(message);
284
285   astring message = keyword + " for "
286     + filename(application_configuration::application_name()).basename().raw()
287     + a_sprintf(": %d of %d unit tests passed.",
288       c_successful.symbols(), c_successful.symbols() + c_failed.symbols());
289   BASE_LOG(message);
290
291   // send an xml file out for the build engine to analyze.
292   write_cppunit_xml();
293
294   return to_return;
295 }
296
297 void unit_base::write_cppunit_xml()
298 {
299   auto_synchronizer synch(c_lock);
300   astring logs_dir = environment::get("LOGS_DIR");
301   if (logs_dir == astring::empty_string()) logs_dir = "logs";  // uhhh.
302   astring outfile = logs_dir + "/"
303       + filename(application_configuration::application_name()).basename().raw()
304       + ".xml";
305
306 //BASE_LOG(astring("outfile is ") + outfile);
307
308   int id = 1;
309
310   xml_generator report;
311   string_table attribs;
312
313   // we are emulating a cppunit xml output.
314
315   report.open_tag("TestRun");
316
317   report.open_tag("FailedTests");
318   for (int i = 0; i < c_failed.symbols(); i++) {
319     attribs.reset();
320     attribs.add("id", a_sprintf("%d", id++));
321     report.open_tag("FailedTest", attribs);
322     attribs.reset();
323 //hmmm: does open_tag eat the attribs?  we could stop worrying about resetting.
324     report.open_tag("Name");
325     report.add_content(c_failed.name(i));
326     report.close_tag("Name");
327
328     report.open_tag("FailureType", attribs);
329     report.add_content("Assertion");
330     report.close_tag("FailureType");
331
332     report.open_tag("Location", attribs);
333
334     report.open_tag("File", attribs);
335     report.add_content(application_configuration::application_name());
336     report.close_tag("File");
337
338     report.open_tag("Line", attribs);
339     report.add_content("0");
340     report.close_tag("Line");
341
342     report.close_tag("Location");
343
344     report.open_tag("Message");
345     report.add_content(c_failed[i]);
346     report.close_tag("Message");
347
348     report.close_tag("FailedTest");
349   }
350   report.close_tag("FailedTests");
351
352   report.open_tag("SuccessfulTests");
353   for (int i = 0; i < c_successful.symbols(); i++) {
354     attribs.reset();
355     attribs.add("id", a_sprintf("%d", id++));
356     attribs.reset();
357     report.open_tag("Test", attribs);
358     report.open_tag("Name");
359     report.add_content(c_successful.name(i));
360     report.close_tag("Name");
361     report.close_tag("Test");
362   }
363   report.close_tag("SuccessfulTests");
364
365   report.open_tag("Statistics");
366   report.open_tag("Tests");
367   report.add_content(a_sprintf("%d", c_failed.symbols() + c_successful.symbols()));
368   report.close_tag("Tests");
369
370   report.open_tag("FailuresTotal");
371   report.add_content(a_sprintf("%d", c_failed.symbols()));
372   report.close_tag("FailuresTotal");
373
374   report.open_tag("Errors");
375   report.add_content("0");
376   report.close_tag("Errors");
377
378   report.open_tag("Failures");
379   report.add_content(a_sprintf("%d", c_failed.symbols()));
380   report.close_tag("Failures");
381
382   report.close_tag("Statistics");
383
384   report.close_tag("TestRun");
385
386   astring text_report = report.generate();
387 //  BASE_LOG(astring("got report\n") + text_report);
388
389   byte_filer xml_out(outfile, "wb");
390   xml_out.write(text_report);
391   xml_out.close();
392 }
393
394 } //namespace.
395