monotone

monotone Mtn Source Tree

Root/unit_tests.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include <map>
12#include <vector>
13#include <iostream>
14#include <fstream>
15#include <iomanip>
16#include <cstdlib>
17#include <cstring>
18#include <cerrno>
19
20#include <boost/function.hpp>
21
22#include "botan/botan.h"
23#include "option.hh"
24#include "unit_tests.hh"
25#include "sanity.hh"
26#include "ui.hh"
27#include "current_exception.hh"
28
29using std::map;
30using std::pair;
31using std::make_pair;
32using std::vector;
33using std::string;
34using std::cout;
35using std::cerr;
36using std::clog;
37using std::exit;
38
39typedef unit_test::unit_test_case test_t;
40typedef map<string const, test_t> test_list_t;
41typedef map<string const, test_list_t> group_list_t;
42
43// This is used by other global constructors, so initialize on demand.
44static group_list_t & unit_tests()
45{
46 static group_list_t tests;
47 return tests;
48}
49
50unit_test::unit_test_case::unit_test_case(char const * group,
51 char const * name,
52 void (*func)())
53 : group(group), name(name), func(func)
54{
55 unit_tests()[group][name] = *this;
56}
57
58unit_test::unit_test_case::unit_test_case()
59{}
60
61// Testsuite state.
62static bool any_test_failed = false;
63static int number_of_failed_tests = 0;
64static int number_of_succeeded_tests = 0;
65static bool log_to_stderr = false;
66
67// Test state.
68static bool this_test_failed;
69
70namespace { struct require_failed {}; }
71
72static void log_state(char const * file, int line,
73 char const * kind, char const * msg)
74{
75 L(FL("%s:%s: %s: %s") % file % line % kind % msg);
76}
77
78// Report what we can about a fatal exception (caught in the outermost catch
79// handlers) which is from the std::exception hierarchy. In this case we
80// can access the exception object.
81static void log_exception(std::exception const & ex)
82{
83 using std::strcmp;
84 using std::strncmp;
85 char const * ex_name = typeid(ex).name();
86 char const * ex_dem = demangle_typename(ex_name);
87 char const * ex_what = ex.what();
88
89 if (ex_dem == 0)
90 ex_dem = ex_name;
91
92 // some demanglers stick "class" at the beginning of their output,
93 // which looks dumb in this context
94 if (!strncmp(ex_dem, "class ", 6))
95 ex_dem += 6;
96
97 // only print what() if it's interesting, i.e. nonempty and different
98 // from the name (mangled or otherwise) of the exception type.
99 if (ex_what == 0 || ex_what[0] == 0
100 || !strcmp(ex_what, ex_name)
101 || !strcmp(ex_what, ex_dem))
102 L(FL("UNCAUGHT EXCEPTION: %s") % ex_dem);
103 else
104 L(FL("UNCAUGHT EXCEPTION: %s: %s") % ex_dem % ex_what);
105}
106
107// Report what we can about a fatal exception (caught in the outermost catch
108// handlers) which is of unknown type. If we have the <cxxabi.h> interfaces,
109// we can at least get the type_info object.
110static void
111log_exception()
112{
113 std::type_info *ex_type = get_current_exception_type();
114 if (ex_type)
115 {
116 char const * ex_name = ex_type->name();
117 char const * ex_dem = demangle_typename(ex_name);
118 if (ex_dem == 0)
119 ex_dem = ex_name;
120 L(FL("UNCAUGHT EXCEPTION: %s") % ex_dem);
121 }
122 else
123 L(FL("UNCAUGHT EXCEPTION: unknown type"));
124}
125
126void unit_test::do_check(bool checkval, char const * file,
127 int line, char const * message)
128{
129 if (!checkval)
130 {
131 this_test_failed = true;
132 log_state(file, line, "CHECK FAILED", message);
133 }
134 else
135 log_state(file, line, "CHECK OK", message);
136}
137
138void unit_test::do_require(bool checkval, char const * file,
139 int line, char const * message)
140{
141 if (!checkval)
142 {
143 this_test_failed = true;
144 log_state(file, line, "REQUIRE FAILED", message);
145 throw require_failed();
146 }
147 else
148 log_state(file, line, "REQUIRE OK", message);
149}
150
151void unit_test::do_checkpoint(char const * file, int line,
152 char const * message)
153{
154 log_state(file, line, "CHECKPOINT", message);
155}
156
157static void run_test(test_t test)
158{
159 this_test_failed = false;
160
161 L(FL("----------------------------------------\n"
162 "Beginning test %s:%s") % test.group % test.name);
163
164 if (!log_to_stderr)
165 {
166 string groupname = string(test.group) + ':' + test.name;
167 cerr << " " << std::left << std::setw(46) << groupname;
168 if (groupname.size() >= 46)
169 cerr << " ";
170 // lack of carriage return is intentional
171 }
172
173 try
174 {
175 test.func();
176 }
177 catch(require_failed &)
178 {
179 // no action required
180 }
181 catch(std::exception & e)
182 {
183 log_exception(e);
184 this_test_failed = true;
185 }
186 catch(...)
187 {
188 log_exception();
189 this_test_failed = true;
190 }
191
192 if (this_test_failed)
193 {
194 ++number_of_failed_tests;
195 L(FL("Test %s:%s failed.\n") % test.group % test.name);
196 if (!log_to_stderr)
197 cerr << "FAIL\n";
198 }
199 else
200 {
201 ++number_of_succeeded_tests;
202 L(FL("Test %s:%s succeeded.\n") % test.group % test.name);
203 if (!log_to_stderr)
204 cerr << "ok\n";
205 }
206
207 if (this_test_failed)
208 any_test_failed = true;
209}
210
211int main(int argc, char * argv[])
212{
213 bool help(false);
214 bool list_groups(false);
215 bool list_tests(false);
216 bool debug(false);
217 string log;
218 vector<string> tests;
219
220 try
221 {
222 option::concrete_option_set os;
223 os("help,h", "display help message", option::setter(help))
224 ("list-groups,l", "list all test groups", option::setter(list_groups))
225 ("list-tests,L", "list all test cases", option::setter(list_tests))
226 ("debug", "write verbose debug log to stderr", option::setter(debug))
227 ("log", "write verbose debug log to this file"
228 " (default is unit_tests.log)", option::setter(log))
229 ("--", "", option::setter(tests));
230
231 os.from_command_line(argc, argv);
232
233 if (help)
234 {
235 cout << (FL("Usage: %s [options] [tests]\nOptions") % argv[0])
236 << os.get_usage_str() << '\n';
237 exit(0);
238 }
239 }
240 catch (option::option_error const & e)
241 {
242 cerr << argv[0] << ": " << e.what() << '\n';
243 exit(2);
244 }
245
246 if (list_groups && list_tests)
247 {
248 cerr << argv[0]
249 << ": only one of --list-groups and --list-tests at a time\n";
250 exit(2);
251 }
252
253 if (list_groups)
254 {
255 for (group_list_t::const_iterator i = unit_tests().begin();
256 i != unit_tests().end(); i++)
257 {
258 if (i->first != "")
259 cout << i->first << '\n';
260 }
261 return 0;
262 }
263
264 if (list_tests)
265 {
266 for (group_list_t::const_iterator i = unit_tests().begin();
267 i != unit_tests().end(); i++)
268 {
269 if (i->first == "")
270 continue;
271 for (test_list_t::const_iterator j = i->second.begin();
272 j != i->second.end(); ++j)
273 {
274 cout << i->first << ":" << j->first << "\n";
275 }
276 }
277 return 0;
278 }
279
280
281 // set up some global state before running the tests
282 ui.initialize();
283 ui.prog_name = argv[0];
284 global_sanity.initialize(argc, argv, "C"); // we didn't call setlocale
285 Botan::LibraryInitializer::initialize();
286
287 if (!debug)
288 {
289 // We would _like_ to use ui.redirect_log_to() but it takes a
290 // system_path and we're not set up to use that here.
291 char const * logname;
292 if (log.empty())
293 logname = "unit_tests.log";
294 else
295 logname = log.c_str();
296
297 std::filebuf * logbuf = new std::filebuf;
298 if (!logbuf->open(logname, std::ios_base::out|std::ios_base::trunc))
299 {
300 char const * syserr = std::strerror(errno);
301 cerr << argv[0] << ": failed to open " << logname
302 << ": " << syserr << '\n';
303 exit(1);
304 }
305 clog.rdbuf(logbuf);
306 // Nobody should be writing to cout, but just in case, send it to
307 // the log.
308 cout.rdbuf(logbuf);
309 }
310 else
311 {
312 if (!log.empty())
313 {
314 cerr << argv[0]
315 << ": only one of --debug and --log at a time\n";
316 exit(2);
317 }
318
319 // Make clog and cout use the same streambuf as cerr; this ensures
320 // that all messages will appear in the order written, no matter what
321 // stream each one is written to.
322 clog.rdbuf(cerr.rdbuf());
323 cout.rdbuf(cerr.rdbuf());
324
325 // Suppress double progress messages.
326 log_to_stderr = true;
327 }
328
329 global_sanity.set_debug();
330
331 if (tests.size() == 0) // run all tests
332 {
333 if (!log_to_stderr)
334 cerr << "Running unit tests...\n";
335
336 for (group_list_t::const_iterator i = unit_tests().begin();
337 i != unit_tests().end(); i++)
338 {
339 if (i->first == "")
340 continue;
341 for (test_list_t::const_iterator j = i->second.begin();
342 j != i->second.end(); ++j)
343 {
344 run_test(j->second);
345 }
346 }
347 }
348 else
349 {
350 bool unrecognized = false;
351
352 vector<test_t> to_run;
353
354 for(vector<string>::const_iterator i = tests.begin();
355 i != tests.end();
356 i++)
357 {
358 string group, test;
359 string::size_type sep = (*i).find(":");
360
361 if (sep >= (*i).length()) // it's a group name
362 group = *i;
363 else
364 {
365 group = (*i).substr(0, sep);
366 test = (*i).substr(sep+1, string::npos);
367 }
368
369 group_list_t::const_iterator g = unit_tests().find(group);
370
371 if (g == unit_tests().end())
372 {
373 unrecognized = true;
374 cerr << argv[0] << ": unrecognized test group: "
375 << group << '\n';
376 continue;
377 }
378
379 if (test == "") // entire group
380 {
381 for (test_list_t::const_iterator t = g->second.begin();
382 t != g->second.end(); ++t)
383 {
384 to_run.push_back(t->second);
385 }
386 }
387 else
388 {
389 test_list_t::const_iterator t = g->second.find(test);
390 if (t == g->second.end())
391 {
392 unrecognized = true;
393 cerr << argv[0] << ": unrecognized test: "
394 << group << ':' << test << '\n';
395 continue;
396 }
397 to_run.push_back(t->second);
398 }
399 }
400
401 if (unrecognized)
402 {
403 return 1;
404 }
405 else
406 {
407 if (!log_to_stderr)
408 cerr << "Running unit tests...\n";
409 for (vector<test_t>::const_iterator i = to_run.begin();
410 i != to_run.end(); ++i)
411 {
412 run_test(*i);
413 }
414 }
415 }
416
417 if (!log_to_stderr)
418 cerr << "\nOf " << (number_of_failed_tests + number_of_succeeded_tests)
419 << " tests run:\n\t"
420 << number_of_succeeded_tests << " succeeded\n\t"
421 << number_of_failed_tests << " failed\n";
422
423 return any_test_failed?1:0;
424}
425
426// Stub for options.cc's sake.
427void
428localize_monotone()
429{
430}
431
432
433// Obviously, these tests are not run by default.
434// They are also not listed by --list-groups or --list-tests .
435// Use "unit_tests :" to run them; they should all fail.
436#include <stdexcept>
437
438UNIT_TEST(, fail_check)
439{
440 UNIT_TEST_CHECKPOINT("checkpoint");
441 UNIT_TEST_CHECK(false);
442 UNIT_TEST_CHECK(false);
443}
444
445UNIT_TEST(, fail_require)
446{
447 UNIT_TEST_CHECKPOINT("checkpoint");
448 UNIT_TEST_REQUIRE(false);
449 UNIT_TEST_CHECK(false);
450}
451
452UNIT_TEST(, fail_throw)
453{
454 UNIT_TEST_CHECK_THROW(string().size(), int);
455}
456
457UNIT_TEST(, fail_nothrow)
458{
459 UNIT_TEST_CHECK_NOT_THROW(throw int(), int);
460}
461
462UNIT_TEST(, uncaught)
463{
464 throw int();
465}
466
467UNIT_TEST(, uncaught_std)
468{
469 throw std::bad_exception();
470}
471
472UNIT_TEST(, uncaught_std_what)
473{
474 throw std::runtime_error("There is no spoon.");
475}
476
477// Local Variables:
478// mode: C++
479// fill-column: 76
480// c-file-style: "gnu"
481// indent-tabs-mode: nil
482// End:
483// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status