| 1 | /////////////////////////////////////////////////////////////////////////////// |
| 2 | // Name: test.cpp |
| 3 | // Purpose: Test program for wxWidgets |
| 4 | // Author: Mike Wetherell |
| 5 | // RCS-ID: $Id$ |
| 6 | // Copyright: (c) 2004 Mike Wetherell |
| 7 | // Licence: wxWidgets licence |
| 8 | /////////////////////////////////////////////////////////////////////////////// |
| 9 | |
| 10 | // ---------------------------------------------------------------------------- |
| 11 | // headers |
| 12 | // ---------------------------------------------------------------------------- |
| 13 | |
| 14 | // For compilers that support precompilation, includes "wx/wx.h" |
| 15 | // and "wx/cppunit.h" |
| 16 | #include "testprec.h" |
| 17 | |
| 18 | #ifdef __BORLANDC__ |
| 19 | #pragma hdrstop |
| 20 | #endif |
| 21 | |
| 22 | // for all others, include the necessary headers |
| 23 | #ifndef WX_PRECOMP |
| 24 | #include "wx/wx.h" |
| 25 | #endif |
| 26 | |
| 27 | #include "wx/beforestd.h" |
| 28 | #ifdef __VISUALC__ |
| 29 | #pragma warning(disable:4100) |
| 30 | #endif |
| 31 | |
| 32 | #include <cppunit/TestListener.h> |
| 33 | #include <cppunit/Protector.h> |
| 34 | #include <cppunit/Test.h> |
| 35 | #include <cppunit/TestResult.h> |
| 36 | #include <cppunit/TestFailure.h> |
| 37 | |
| 38 | #ifdef __VISUALC__ |
| 39 | #pragma warning(default:4100) |
| 40 | #endif |
| 41 | #include "wx/afterstd.h" |
| 42 | |
| 43 | #include "wx/cmdline.h" |
| 44 | #include <iostream> |
| 45 | |
| 46 | #ifdef __WXMSW__ |
| 47 | #include "wx/msw/msvcrt.h" |
| 48 | #endif |
| 49 | |
| 50 | #ifdef __WXOSX__ |
| 51 | #include "wx/osx/private.h" |
| 52 | #endif |
| 53 | |
| 54 | #include "wx/socket.h" |
| 55 | |
| 56 | using namespace std; |
| 57 | |
| 58 | using CppUnit::Test; |
| 59 | using CppUnit::TestSuite; |
| 60 | using CppUnit::TestFactoryRegistry; |
| 61 | |
| 62 | |
| 63 | // ---------------------------------------------------------------------------- |
| 64 | // helper classes |
| 65 | // ---------------------------------------------------------------------------- |
| 66 | |
| 67 | // exception class for MSVC debug CRT assertion failures |
| 68 | #ifdef wxUSE_VC_CRTDBG |
| 69 | |
| 70 | struct CrtAssertFailure |
| 71 | { |
| 72 | CrtAssertFailure(const char *message) : m_msg(message) { } |
| 73 | |
| 74 | const wxString m_msg; |
| 75 | |
| 76 | wxDECLARE_NO_ASSIGN_CLASS(CrtAssertFailure); |
| 77 | }; |
| 78 | |
| 79 | #endif // wxUSE_VC_CRTDBG |
| 80 | |
| 81 | // this function should only be called from a catch clause |
| 82 | static string GetExceptionMessage() |
| 83 | { |
| 84 | wxString msg; |
| 85 | |
| 86 | try |
| 87 | { |
| 88 | throw; |
| 89 | } |
| 90 | #if wxDEBUG_LEVEL |
| 91 | catch ( TestAssertFailure& e ) |
| 92 | { |
| 93 | msg << "wxWidgets assert: " << e.m_cond << " failed " |
| 94 | "at " << e.m_file << ":" << e.m_line << " in " << e.m_func |
| 95 | << " with message '" << e.m_msg << "'"; |
| 96 | } |
| 97 | #endif // wxDEBUG_LEVEL |
| 98 | #ifdef wxUSE_VC_CRTDBG |
| 99 | catch ( CrtAssertFailure& e ) |
| 100 | { |
| 101 | msg << "CRT assert failure: " << e.m_msg; |
| 102 | } |
| 103 | #endif // wxUSE_VC_CRTDBG |
| 104 | catch ( std::exception& e ) |
| 105 | { |
| 106 | msg << "std::exception: " << e.what(); |
| 107 | } |
| 108 | catch ( ... ) |
| 109 | { |
| 110 | msg = "Unknown exception caught."; |
| 111 | } |
| 112 | |
| 113 | return string(msg.mb_str()); |
| 114 | } |
| 115 | |
| 116 | // Protector adding handling of wx-specific (this includes MSVC debug CRT in |
| 117 | // this context) exceptions |
| 118 | class wxUnitTestProtector : public CppUnit::Protector |
| 119 | { |
| 120 | public: |
| 121 | virtual bool protect(const CppUnit::Functor &functor, |
| 122 | const CppUnit::ProtectorContext& context) |
| 123 | { |
| 124 | try |
| 125 | { |
| 126 | return functor(); |
| 127 | } |
| 128 | catch ( std::exception& ) |
| 129 | { |
| 130 | // cppunit deals with the standard exceptions itself, let it do as |
| 131 | // it output more details (especially for std::exception-derived |
| 132 | // CppUnit::Exception) than we do |
| 133 | throw; |
| 134 | } |
| 135 | catch ( ... ) |
| 136 | { |
| 137 | reportError(context, CppUnit::Message("Uncaught exception", |
| 138 | GetExceptionMessage())); |
| 139 | } |
| 140 | |
| 141 | return false; |
| 142 | } |
| 143 | }; |
| 144 | |
| 145 | // Displays the test name before starting to execute it: this helps with |
| 146 | // diagnosing where exactly does a test crash or hang when/if it does. |
| 147 | class DetailListener : public CppUnit::TestListener |
| 148 | { |
| 149 | public: |
| 150 | DetailListener(bool doTiming = false): |
| 151 | CppUnit::TestListener(), |
| 152 | m_timing(doTiming) |
| 153 | { |
| 154 | } |
| 155 | |
| 156 | virtual void startTest(CppUnit::Test *test) |
| 157 | { |
| 158 | wxPrintf(" %-60s ", test->getName()); |
| 159 | m_result = RESULT_OK; |
| 160 | m_watch.Start(); |
| 161 | } |
| 162 | |
| 163 | virtual void addFailure(const CppUnit::TestFailure& failure) |
| 164 | { |
| 165 | m_result = failure.isError() ? RESULT_ERROR : RESULT_FAIL; |
| 166 | } |
| 167 | |
| 168 | virtual void endTest(CppUnit::Test * WXUNUSED(test)) |
| 169 | { |
| 170 | m_watch.Pause(); |
| 171 | wxPrintf(GetResultStr(m_result)); |
| 172 | if (m_timing) |
| 173 | wxPrintf(" %6d ms", m_watch.Time()); |
| 174 | wxPrintf("\n"); |
| 175 | } |
| 176 | |
| 177 | protected : |
| 178 | enum ResultType |
| 179 | { |
| 180 | RESULT_OK = 0, |
| 181 | RESULT_FAIL, |
| 182 | RESULT_ERROR, |
| 183 | RESULT_MAX |
| 184 | }; |
| 185 | |
| 186 | wxString GetResultStr(ResultType type) const |
| 187 | { |
| 188 | static const char *resultTypeNames[] = |
| 189 | { |
| 190 | " OK", |
| 191 | "FAIL", |
| 192 | " ERR" |
| 193 | }; |
| 194 | |
| 195 | wxCOMPILE_TIME_ASSERT( WXSIZEOF(resultTypeNames) == RESULT_MAX, |
| 196 | ResultTypeNamesMismatch ); |
| 197 | |
| 198 | return resultTypeNames[type]; |
| 199 | } |
| 200 | |
| 201 | bool m_timing; |
| 202 | wxStopWatch m_watch; |
| 203 | ResultType m_result; |
| 204 | }; |
| 205 | |
| 206 | #if wxUSE_GUI |
| 207 | typedef wxApp TestAppBase; |
| 208 | #else |
| 209 | typedef wxAppConsole TestAppBase; |
| 210 | #endif |
| 211 | |
| 212 | // The application class |
| 213 | // |
| 214 | class TestApp : public TestAppBase |
| 215 | { |
| 216 | public: |
| 217 | TestApp(); |
| 218 | |
| 219 | // standard overrides |
| 220 | virtual void OnInitCmdLine(wxCmdLineParser& parser); |
| 221 | virtual bool OnCmdLineParsed(wxCmdLineParser& parser); |
| 222 | virtual bool OnInit(); |
| 223 | virtual int OnRun(); |
| 224 | virtual int OnExit(); |
| 225 | |
| 226 | // used by events propagation test |
| 227 | virtual int FilterEvent(wxEvent& event); |
| 228 | virtual bool ProcessEvent(wxEvent& event); |
| 229 | |
| 230 | void SetFilterEventFunc(FilterEventFunc f) { m_filterEventFunc = f; } |
| 231 | void SetProcessEventFunc(ProcessEventFunc f) { m_processEventFunc = f; } |
| 232 | |
| 233 | private: |
| 234 | void List(Test *test, const string& parent = "") const; |
| 235 | |
| 236 | // call List() if m_list or runner.addTest() otherwise |
| 237 | void AddTest(CppUnit::TestRunner& runner, Test *test) |
| 238 | { |
| 239 | if (m_list) |
| 240 | List(test); |
| 241 | else |
| 242 | runner.addTest(test); |
| 243 | } |
| 244 | |
| 245 | // command lines options/parameters |
| 246 | bool m_list; |
| 247 | bool m_longlist; |
| 248 | bool m_detail; |
| 249 | bool m_timing; |
| 250 | wxArrayString m_registries; |
| 251 | wxLocale *m_locale; |
| 252 | |
| 253 | // event handling hooks |
| 254 | FilterEventFunc m_filterEventFunc; |
| 255 | ProcessEventFunc m_processEventFunc; |
| 256 | }; |
| 257 | |
| 258 | IMPLEMENT_APP_NO_MAIN(TestApp) |
| 259 | |
| 260 | |
| 261 | // ---------------------------------------------------------------------------- |
| 262 | // global functions |
| 263 | // ---------------------------------------------------------------------------- |
| 264 | |
| 265 | #ifdef wxUSE_VC_CRTDBG |
| 266 | |
| 267 | static int TestCrtReportHook(int reportType, char *message, int *) |
| 268 | { |
| 269 | if ( reportType != _CRT_ASSERT ) |
| 270 | return FALSE; |
| 271 | |
| 272 | throw CrtAssertFailure(message); |
| 273 | } |
| 274 | |
| 275 | #endif // wxUSE_VC_CRTDBG |
| 276 | |
| 277 | #if wxDEBUG_LEVEL |
| 278 | |
| 279 | static void TestAssertHandler(const wxString& file, |
| 280 | int line, |
| 281 | const wxString& func, |
| 282 | const wxString& cond, |
| 283 | const wxString& msg) |
| 284 | { |
| 285 | throw TestAssertFailure(file, line, func, cond, msg); |
| 286 | } |
| 287 | |
| 288 | #endif // wxDEBUG_LEVEL |
| 289 | |
| 290 | int main(int argc, char **argv) |
| 291 | { |
| 292 | // tests can be ran non-interactively so make sure we don't show any assert |
| 293 | // dialog boxes -- neither our own nor from MSVC debug CRT -- which would |
| 294 | // prevent them from completing |
| 295 | |
| 296 | #if wxDEBUG_LEVEL |
| 297 | wxSetAssertHandler(TestAssertHandler); |
| 298 | #endif // wxDEBUG_LEVEL |
| 299 | |
| 300 | #ifdef wxUSE_VC_CRTDBG |
| 301 | _CrtSetReportHook(TestCrtReportHook); |
| 302 | #endif // wxUSE_VC_CRTDBG |
| 303 | |
| 304 | try |
| 305 | { |
| 306 | return wxEntry(argc, argv); |
| 307 | } |
| 308 | catch ( ... ) |
| 309 | { |
| 310 | cerr << "\n" << GetExceptionMessage() << endl; |
| 311 | } |
| 312 | |
| 313 | return -1; |
| 314 | } |
| 315 | |
| 316 | extern void SetFilterEventFunc(FilterEventFunc func) |
| 317 | { |
| 318 | wxGetApp().SetFilterEventFunc(func); |
| 319 | } |
| 320 | |
| 321 | extern void SetProcessEventFunc(ProcessEventFunc func) |
| 322 | { |
| 323 | wxGetApp().SetProcessEventFunc(func); |
| 324 | } |
| 325 | |
| 326 | extern bool IsNetworkAvailable() |
| 327 | { |
| 328 | // NOTE: we could use wxDialUpManager here if it was in wxNet; since it's in |
| 329 | // wxCore we use a simple rough test: |
| 330 | |
| 331 | wxSocketBase::Initialize(); |
| 332 | |
| 333 | wxIPV4address addr; |
| 334 | if (!addr.Hostname("www.google.com") || !addr.Service("www")) |
| 335 | { |
| 336 | wxSocketBase::Shutdown(); |
| 337 | return false; |
| 338 | } |
| 339 | |
| 340 | wxSocketClient sock; |
| 341 | sock.SetTimeout(10); // 10 secs |
| 342 | bool online = sock.Connect(addr); |
| 343 | |
| 344 | wxSocketBase::Shutdown(); |
| 345 | |
| 346 | return online; |
| 347 | } |
| 348 | |
| 349 | // helper of OnRun(): gets the test with the given name, returning NULL (and |
| 350 | // not an empty test suite) if there is no such test |
| 351 | static Test *GetTestByName(const wxString& name) |
| 352 | { |
| 353 | Test * |
| 354 | test = TestFactoryRegistry::getRegistry(string(name.mb_str())).makeTest(); |
| 355 | if ( test ) |
| 356 | { |
| 357 | TestSuite * const suite = dynamic_cast<TestSuite *>(test); |
| 358 | if ( !suite || !suite->countTestCases() ) |
| 359 | { |
| 360 | // it's a bogus test, don't use it |
| 361 | delete test; |
| 362 | test = NULL; |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | return test; |
| 367 | } |
| 368 | |
| 369 | |
| 370 | // ---------------------------------------------------------------------------- |
| 371 | // TestApp |
| 372 | // ---------------------------------------------------------------------------- |
| 373 | |
| 374 | TestApp::TestApp() |
| 375 | : m_list(false), |
| 376 | m_longlist(false) |
| 377 | { |
| 378 | m_filterEventFunc = NULL; |
| 379 | m_processEventFunc = NULL; |
| 380 | |
| 381 | m_locale = NULL; |
| 382 | } |
| 383 | |
| 384 | // Init |
| 385 | // |
| 386 | bool TestApp::OnInit() |
| 387 | { |
| 388 | if ( !TestAppBase::OnInit() ) |
| 389 | return false; |
| 390 | |
| 391 | #if wxUSE_GUI |
| 392 | cout << "Test program for wxWidgets GUI features\n" |
| 393 | #else |
| 394 | cout << "Test program for wxWidgets non-GUI features\n" |
| 395 | #endif |
| 396 | << "build: " << WX_BUILD_OPTIONS_SIGNATURE << std::endl; |
| 397 | |
| 398 | #if wxUSE_GUI |
| 399 | // create a hidden parent window to be used as parent for the GUI controls |
| 400 | new wxFrame(NULL, wxID_ANY, "Hidden wx test frame"); |
| 401 | #endif // wxUSE_GUI |
| 402 | |
| 403 | return true; |
| 404 | } |
| 405 | |
| 406 | // The table of command line options |
| 407 | // |
| 408 | void TestApp::OnInitCmdLine(wxCmdLineParser& parser) |
| 409 | { |
| 410 | TestAppBase::OnInitCmdLine(parser); |
| 411 | |
| 412 | static const wxCmdLineEntryDesc cmdLineDesc[] = { |
| 413 | { wxCMD_LINE_SWITCH, "l", "list", |
| 414 | "list the test suites, do not run them", |
| 415 | wxCMD_LINE_VAL_NONE, 0 }, |
| 416 | { wxCMD_LINE_SWITCH, "L", "longlist", |
| 417 | "list the test cases, do not run them", |
| 418 | wxCMD_LINE_VAL_NONE, 0 }, |
| 419 | { wxCMD_LINE_SWITCH, "d", "detail", |
| 420 | "print the test case names, run them", |
| 421 | wxCMD_LINE_VAL_NONE, 0 }, |
| 422 | { wxCMD_LINE_SWITCH, "t", "timing", |
| 423 | "print names and mesure running time of individual test, run them", |
| 424 | wxCMD_LINE_VAL_NONE, 0 }, |
| 425 | { wxCMD_LINE_OPTION, "", "locale", |
| 426 | "locale to use when running the program", |
| 427 | wxCMD_LINE_VAL_STRING, 0 }, |
| 428 | { wxCMD_LINE_PARAM, NULL, NULL, "REGISTRY", wxCMD_LINE_VAL_STRING, |
| 429 | wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE }, |
| 430 | wxCMD_LINE_DESC_END |
| 431 | }; |
| 432 | |
| 433 | parser.SetDesc(cmdLineDesc); |
| 434 | } |
| 435 | |
| 436 | // Handle command line options |
| 437 | // |
| 438 | bool TestApp::OnCmdLineParsed(wxCmdLineParser& parser) |
| 439 | { |
| 440 | if (parser.GetParamCount()) |
| 441 | { |
| 442 | for (size_t i = 0; i < parser.GetParamCount(); i++) |
| 443 | m_registries.push_back(parser.GetParam(i)); |
| 444 | } |
| 445 | |
| 446 | m_longlist = parser.Found("longlist"); |
| 447 | m_list = m_longlist || parser.Found("list"); |
| 448 | m_timing = parser.Found("timing"); |
| 449 | m_detail = !m_timing && parser.Found("detail"); |
| 450 | |
| 451 | wxString loc; |
| 452 | if ( parser.Found("locale", &loc) ) |
| 453 | { |
| 454 | const wxLanguageInfo * const info = wxLocale::FindLanguageInfo(loc); |
| 455 | if ( !info ) |
| 456 | { |
| 457 | cerr << "Locale \"" << string(loc.mb_str()) << "\" is unknown.\n"; |
| 458 | return false; |
| 459 | } |
| 460 | |
| 461 | m_locale = new wxLocale(info->Language); |
| 462 | if ( !m_locale->IsOk() ) |
| 463 | { |
| 464 | cerr << "Using locale \"" << string(loc.mb_str()) << "\" failed.\n"; |
| 465 | return false; |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | return TestAppBase::OnCmdLineParsed(parser); |
| 470 | } |
| 471 | |
| 472 | // Event handling |
| 473 | int TestApp::FilterEvent(wxEvent& event) |
| 474 | { |
| 475 | if ( m_filterEventFunc ) |
| 476 | return (*m_filterEventFunc)(event); |
| 477 | |
| 478 | return TestAppBase::FilterEvent(event); |
| 479 | } |
| 480 | |
| 481 | bool TestApp::ProcessEvent(wxEvent& event) |
| 482 | { |
| 483 | if ( m_processEventFunc ) |
| 484 | return (*m_processEventFunc)(event); |
| 485 | |
| 486 | return TestAppBase::ProcessEvent(event); |
| 487 | } |
| 488 | |
| 489 | // Run |
| 490 | // |
| 491 | int TestApp::OnRun() |
| 492 | { |
| 493 | #if wxUSE_GUI |
| 494 | #ifdef __WXOSX__ |
| 495 | // make sure there's always an autorelease pool ready |
| 496 | wxMacAutoreleasePool autoreleasepool; |
| 497 | #endif |
| 498 | #endif |
| 499 | |
| 500 | #if wxUSE_LOG |
| 501 | // Switch off logging unless --verbose |
| 502 | bool verbose = wxLog::GetVerbose(); |
| 503 | wxLog::EnableLogging(verbose); |
| 504 | #else |
| 505 | bool verbose = false; |
| 506 | #endif |
| 507 | |
| 508 | CppUnit::TextTestRunner runner; |
| 509 | |
| 510 | if ( m_registries.empty() ) |
| 511 | { |
| 512 | // run or list all tests |
| 513 | AddTest(runner, TestFactoryRegistry::getRegistry().makeTest()); |
| 514 | } |
| 515 | else // run only the selected tests |
| 516 | { |
| 517 | for (size_t i = 0; i < m_registries.size(); i++) |
| 518 | { |
| 519 | const wxString reg = m_registries[i]; |
| 520 | Test *test = GetTestByName(reg); |
| 521 | |
| 522 | if ( !test && !reg.EndsWith("TestCase") ) |
| 523 | { |
| 524 | test = GetTestByName(reg + "TestCase"); |
| 525 | } |
| 526 | |
| 527 | if ( !test ) |
| 528 | { |
| 529 | cerr << "No such test suite: " << string(reg.mb_str()) << endl; |
| 530 | return 2; |
| 531 | } |
| 532 | |
| 533 | AddTest(runner, test); |
| 534 | } |
| 535 | } |
| 536 | |
| 537 | if ( m_list ) |
| 538 | return EXIT_SUCCESS; |
| 539 | |
| 540 | runner.setOutputter(new CppUnit::CompilerOutputter(&runner.result(), cout)); |
| 541 | |
| 542 | // there is a bug |
| 543 | // (http://sf.net/tracker/index.php?func=detail&aid=1649369&group_id=11795&atid=111795) |
| 544 | // in some versions of cppunit: they write progress dots to cout (and not |
| 545 | // cerr) and don't flush it so all the dots appear at once at the end which |
| 546 | // is not very useful so unbuffer cout to work around this |
| 547 | cout.setf(ios::unitbuf); |
| 548 | |
| 549 | // add detail listener if needed |
| 550 | DetailListener detailListener(m_timing); |
| 551 | if ( m_detail || m_timing ) |
| 552 | runner.eventManager().addListener(&detailListener); |
| 553 | |
| 554 | // finally ensure that we report our own exceptions nicely instead of |
| 555 | // giving "uncaught exception of unknown type" messages |
| 556 | runner.eventManager().pushProtector(new wxUnitTestProtector); |
| 557 | |
| 558 | bool printProgress = !(verbose || m_detail || m_timing); |
| 559 | return runner.run("", false, true, printProgress) ? EXIT_SUCCESS : EXIT_FAILURE; |
| 560 | } |
| 561 | |
| 562 | int TestApp::OnExit() |
| 563 | { |
| 564 | delete m_locale; |
| 565 | |
| 566 | #if wxUSE_GUI |
| 567 | delete GetTopWindow(); |
| 568 | #endif // wxUSE_GUI |
| 569 | |
| 570 | return 0; |
| 571 | } |
| 572 | |
| 573 | // List the tests |
| 574 | // |
| 575 | void TestApp::List(Test *test, const string& parent /*=""*/) const |
| 576 | { |
| 577 | TestSuite *suite = dynamic_cast<TestSuite*>(test); |
| 578 | string name; |
| 579 | |
| 580 | if (suite) { |
| 581 | // take the last component of the name and append to the parent |
| 582 | name = test->getName(); |
| 583 | string::size_type i = name.find_last_of(".:"); |
| 584 | if (i != string::npos) |
| 585 | name = name.substr(i + 1); |
| 586 | name = parent + "." + name; |
| 587 | |
| 588 | // drop the 1st component from the display and indent |
| 589 | if (parent != "") { |
| 590 | string::size_type j = i = name.find('.', 1); |
| 591 | while ((j = name.find('.', j + 1)) != string::npos) |
| 592 | cout << " "; |
| 593 | cout << " " << name.substr(i + 1) << "\n"; |
| 594 | } |
| 595 | |
| 596 | typedef vector<Test*> Tests; |
| 597 | typedef Tests::const_iterator Iter; |
| 598 | |
| 599 | const Tests& tests = suite->getTests(); |
| 600 | |
| 601 | for (Iter it = tests.begin(); it != tests.end(); ++it) |
| 602 | List(*it, name); |
| 603 | } |
| 604 | else if (m_longlist) { |
| 605 | string::size_type i = 0; |
| 606 | while ((i = parent.find('.', i + 1)) != string::npos) |
| 607 | cout << " "; |
| 608 | cout << " " << test->getName() << "\n"; |
| 609 | } |
| 610 | } |