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