diff options
| author | Matthias Melcher <github@matthiasm.com> | 2023-02-23 15:42:05 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-23 15:42:05 +0100 |
| commit | 9f87af8ad9a3d8112518b6bbb5b6075881a5b9a2 (patch) | |
| tree | fe96512734abb7b29bc6b8fac9816d7cc0c4ae3f /test/unittests.cxx | |
| parent | 92818939267fc7fbb6a33d86fb78576f55ce6aca (diff) | |
Fl_String refactoring and extension (#683)
- add true unittest and Fl_String testing
- interface and printout are similar to gtest
without requiring external linkage.
just run `unittest --core`.
- new Fl_String API
- extended API to fl_input_str and fl_password_str
- co-authored-by: Albrecht Schlosser <albrechts.fltk@online.de>
Diffstat (limited to 'test/unittests.cxx')
| -rw-r--r-- | test/unittests.cxx | 319 |
1 files changed, 313 insertions, 6 deletions
diff --git a/test/unittests.cxx b/test/unittests.cxx index e393613fb..b9103feb5 100644 --- a/test/unittests.cxx +++ b/test/unittests.cxx @@ -29,10 +29,12 @@ #include <FL/Fl_Double_Window.H> #include <FL/Fl_Hold_Browser.H> #include <FL/Fl_Help_View.H> +#include <FL/Fl_Simple_Terminal.H> #include <FL/Fl_Group.H> #include <FL/Fl_Box.H> #include <FL/fl_draw.H> // fl_text_extents() #include <FL/fl_string_functions.h> // fl_strdup() +#include <FL/fl_ask.H> // fl_message() #include <stdlib.h> // malloc, free class Ut_Main_Window *mainwin = NULL; @@ -41,6 +43,8 @@ class Fl_Hold_Browser *browser = NULL; int UnitTest::num_tests_ = 0; UnitTest *UnitTest::test_list_[200] = { 0 }; +// ----- UnitTest ------------------------------------------------------ MARK: - + UnitTest::UnitTest(int index, const char* label, Fl_Widget* (*create)()) : widget_(0L) { @@ -77,6 +81,8 @@ void UnitTest::add(int index, UnitTest* t) { num_tests_ = index+1; } +// ----- Ut_Main_Window ------------------------------------------------ MARK: - + Ut_Main_Window::Ut_Main_Window(int w, int h, const char *l) : Fl_Double_Window(w, h, l), draw_alignment_test_(0) @@ -118,10 +124,265 @@ void Ut_Main_Window::test_alignment(int v) { redraw(); } -//------- include the various unit tests as inline code ------- +// ----- Ut_Test ------------------------------------------------------- MARK: - + +/** Create a unit test that can contain many test cases using EXPECT_*. + + Ut_Test should be instantiated by using the TEST(SUITE, NAME) macro. Multiple + TEST() macros can be called within the same source file to generate an + arbitrary number of tests. TEST() can be called in multiple source files + of the same application. + + The constructor also registers the test with Ut_Suite and groups it by name. + */ +Ut_Test::Ut_Test(const char *suitename, const char *testname, Ut_Test_Call call) +: name_(testname), + call_(call), + failed_(false), + done_(false) +{ + Ut_Suite *suite = Ut_Suite::locate(suitename); + suite->add(this); +} + +/** Run all cases inside the test and return false when the first case fails. */ +bool Ut_Test::run(const char *suite) { + Ut_Suite::printf("%s[ RUN ]%s %s.%s\n", + Ut_Suite::green, Ut_Suite::normal, suite, name_); + bool ret = call_(); + if (ret) { + Ut_Suite::printf("%s[ OK ]%s %s.%s\n", + Ut_Suite::green, Ut_Suite::normal, suite, name_); + failed_ = false; + } else { + Ut_Suite::printf("%s[ FAILED ]%s %s.%s\n", + Ut_Suite::red, Ut_Suite::normal, suite, name_); + failed_ = true; + } + done_ = true; + return ret; +} + +/** Print a message is the test was previously marked failed. */ +void Ut_Test::print_failed(const char *suite) { + if (failed_) { + Ut_Suite::printf("%s[ FAILED ]%s %s.%s\n", + Ut_Suite::red, Ut_Suite::normal, suite, name_); + } +} + +// ----- Ut_Suite ------------------------------------------------------ MARK: - + +Ut_Suite **Ut_Suite::suite_list_ = NULL; +int Ut_Suite::suite_list_size_ = 0; +int Ut_Suite::num_tests_ = 0; +int Ut_Suite::num_passed_ = 0; +int Ut_Suite::num_failed_ = 0; +const char *Ut_Suite::red = "\033[31m"; +const char *Ut_Suite::green = "\033[32m"; +const char *Ut_Suite::normal = "\033[0m"; +Fl_Simple_Terminal *Ut_Suite::tty = NULL; + +/** Switch the user of color escape sequnces in the log text. */ +void Ut_Suite::color(int v) { + if (v) { + red = "\033[31m"; + green = "\033[32m"; + normal = "\033[0m"; + } else { + red = ""; + green = ""; + normal = ""; + } +} + +/** Create a suite that will group tests by the suite name. + + Ut_Suite is automatically instantiated by using the TEST(SUITE, NAME) macro. + Multiple TEST() macros are grouped into suits by suite name. + */ +Ut_Suite::Ut_Suite(const char *name) +: test_list_(NULL), + test_list_size_(0), + name_(name), + done_(false) +{ +} + +/** Add a test to the suite. This is done automatically by the TEST() macro. */ +void Ut_Suite::add(Ut_Test *test) { + if ( (test_list_size_ % 16) == 0 ) { + test_list_ = (Ut_Test**)realloc(test_list_, (test_list_size_+16)*sizeof(Ut_Test*)); + } + test_list_[test_list_size_++] = test; +} + +/** Static method that will find or create a suite by name. */ +Ut_Suite *Ut_Suite::locate(const char *name) { + for (int i=0; i<suite_list_size_; i++) { + if (strcmp(name, suite_list_[i]->name_)==0) + return suite_list_[i]; + } + if ( (suite_list_size_ % 16) == 0 ) { + suite_list_ = (Ut_Suite**)realloc(suite_list_, (suite_list_size_+16)*sizeof(Ut_Suite*)); + } + Ut_Suite *s = new Ut_Suite(name); + suite_list_[suite_list_size_++] = s; + return s; +} + +/** Logs the start of a test suite run. */ +void Ut_Suite::print_suite_epilog() { + Ut_Suite::printf("%s[----------]%s %d test%s from %s\n", Ut_Suite::green, Ut_Suite::normal, + test_list_size_, test_list_size_ == 1 ? "" : "s", name_); +} + +/** Run all tests in a single suite, returning the number of failed tests. */ +int Ut_Suite::run() { + print_suite_epilog(); + int num_tests_failed = 0; + for (int i=0; i<test_list_size_; i++) { + if (!test_list_[i]->run(name_)) { + num_tests_failed++; + } + } + return num_tests_failed; +} + +/** Static method to log all tests that are marked failed. */ +void Ut_Suite::print_failed() { + for (int i=0; i<test_list_size_; i++) { + test_list_[i]->print_failed(name_); + } +} + +/** Static method to log the start of a test run. */ +void Ut_Suite::print_prolog() { + int i; + num_tests_ = 0; + num_passed_ = 0; + num_failed_ = 0; + for (i=0; i<suite_list_size_; i++) { + num_tests_ += suite_list_[i]->size(); + } + Ut_Suite::printf("%s[==========]%s Running %d tests from %d test case%s.\n", + Ut_Suite::green, Ut_Suite::normal, + num_tests_, suite_list_size_, + suite_list_size_ == 1 ? "" : "s"); +} + +/** Static method to log the end of a test run. */ +void Ut_Suite::print_epilog() { + int i; + Ut_Suite::printf("%s[==========]%s %d tests from %d test case%s ran.\n", + Ut_Suite::green, Ut_Suite::normal, + num_tests_, suite_list_size_, + suite_list_size_ == 1 ? "" : "s"); + if (num_passed_) { + Ut_Suite::printf("%s[ PASSED ]%s %d test%s.\n", + Ut_Suite::green, Ut_Suite::normal, num_passed_, + num_passed_ == 1 ? "" : "s"); + } + if (num_failed_) { + Ut_Suite::printf("%s[ FAILED ]%s %d test%s, listed below:\n", + Ut_Suite::red, Ut_Suite::normal, num_failed_, + num_failed_ == 1 ? "" : "s"); + } + for (i=0; i<suite_list_size_; i++) { + suite_list_[i]->print_failed(); + } +} + +/** Static method to run all tests in all test suites. + Returns the number of failed tests. + */ +int Ut_Suite::run_all_tests() { + print_prolog(); + // loop through all suites which then loop through all tests + for (int i=0; i<suite_list_size_; i++) { + int n = suite_list_[i]->run(); + num_passed_ += suite_list_[i]->size() - n; + num_failed_ += n; + } + print_epilog(); + return num_failed_; +} + +/** Static method to run all test, one-by-one, until done. + Run all tests by calling `while (Ut_Suite::run_next_test()) { }`. + This is used to visualise test progress with the terminal window by runnig test + asynchronously and adding a noticable delay between calls. + */ +bool Ut_Suite::run_next_test() { + // if all suites are done, print the ending text and return + Ut_Suite *last = suite_list_[suite_list_size_-1]; + if (last->done_) { + print_epilog(); + return false; + } + // if no tests ran yet, print the starting text + Ut_Suite *first = suite_list_[0]; + if (!first->done_ && !first->test_list_[0]->done_) { + print_prolog(); + } + // now find the next test that hasn't ran yet + for (int i=0; i<suite_list_size_; i++) { + Ut_Suite *st = suite_list_[i]; + if (st->done_) continue; + if (!st->test_list_[0]->done_) + st->print_suite_epilog(); + for (int j=0; j<st->test_list_size_; j++) { + if (st->test_list_[j]->done_) continue; + if (st->test_list_[j]->run(st->name_)) num_passed_++; else num_failed_++; + return true; + } + st->done_ = true; + return true; + } + return true; +} + +/** A printf that is redirected to the terminal or stdout. */ +void Ut_Suite::printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + if (tty) { + tty->vprintf(format, args); + } else { + vprintf(format, args); + } + va_end(args); +} + +/** Log the result of a boolean case fail. */ +void Ut_Suite::log_bool(const char *file, int line, const char *cond, bool result, bool expected) { + Ut_Suite::printf("%s(%d): error:\n", file, line); + Ut_Suite::printf("Value of: %s\n", cond); + Ut_Suite::printf("Actual: %s\n", result ? "true" : "false"); + Ut_Suite::printf("Expected: %s\n", expected ? "true" : "false"); +} + +/** Log the result of a string comparison case fail. */ +void Ut_Suite::log_string(const char *file, int line, const char *cond, const char *result, const char *expected) { + Ut_Suite::printf("%s(%d): error:\n", file, line); + Ut_Suite::printf("Value of: %s\n", cond); + Ut_Suite::printf(" Actual: %s\n", result); + Ut_Suite::printf("Expected: %s\n", expected); +} + +/** Log the result of an integer comparison case fail. */ +void Ut_Suite::log_int(const char *file, int line, const char *cond, int result, const char *expected) { + Ut_Suite::printf("%s(%d): error:\n", file, line); + Ut_Suite::printf("Value of: %s\n", cond); + Ut_Suite::printf(" Actual: %d\n", result); + Ut_Suite::printf("Expected: %s\n", expected); +} + +// ----- main ---------------------------------------------------------- MARK: - // callback whenever the browser value changes -void UT_BROWSER_CB(Fl_Widget*, void*) { +void ui_browser_cb(Fl_Widget*, void*) { for ( int t=1; t<=browser->size(); t++ ) { UnitTest* ti = (UnitTest*)browser->data(t); if ( browser->selected(t) ) { @@ -132,11 +393,57 @@ void UT_BROWSER_CB(Fl_Widget*, void*) { } } +static bool run_core_tests_only = false; + +static int arg(int argc, char** argv, int& i) { + if ( strcmp(argv[i], "--core") == 0 ) { + run_core_tests_only = true; + i++; + return 1; + } + if ( strcmp(argv[i], "--color=0") == 0 ) { + Ut_Suite::color(0); + i++; + return 1; + } + if ( strcmp(argv[i], "--color=1") == 0 ) { + Ut_Suite::color(1); + i++; + return 1; + } + if ( (strcmp(argv[i], "--help") == 0) || (strcmp(argv[i], "-h") == 0) ) { + return 0; + } + return 0; +} // This is the main call. It creates the window and adds all previously // registered tests to the browser widget. int main(int argc, char** argv) { - Fl::args(argc, argv); + int i; + if ( Fl::args(argc,argv,i,arg) == 0 ) { // unsupported argument found + static const char *msg = + "usage: %s <switches>\n" + " --core : test core functionality only\n" + " --color=1, --color=0 : print test output in color or plain text" + " --help, -h : print this help page\n"; + const char *app_name = NULL; + if ( (argc > 0) && argv[0] && argv[0][0] ) + app_name = fl_filename_name(argv[0]); + if ( !app_name || !app_name[0]) + app_name = "unittests"; +#ifdef _MSC_VER + fl_message(msg, app_name); +#else + fprintf(stderr, msg, app_name); +#endif + return 1; + } + + if (run_core_tests_only) { + return RUN_ALL_TESTS(); + } + Fl::get_system_colors(); Fl::scheme(Fl::scheme()); // init scheme before instantiating tests Fl::visual(FL_RGB); @@ -146,9 +453,9 @@ int main(int argc, char** argv) { browser = new Fl_Hold_Browser(UT_BROWSER_X, UT_BROWSER_Y, UT_BROWSER_W, UT_BROWSER_H, "Unit Tests"); browser->align(FL_ALIGN_TOP|FL_ALIGN_LEFT); browser->when(FL_WHEN_CHANGED); - browser->callback(UT_BROWSER_CB); + browser->callback(ui_browser_cb); - int i, n = UnitTest::num_tests(); + int n = UnitTest::num_tests(); for (i=0; i<n; i++) { UnitTest* t = UnitTest::test(i); if (t) { @@ -163,6 +470,6 @@ int main(int argc, char** argv) { mainwin->show(argc, argv); // Select first test in browser, and show that test. browser->select(UT_TEST_ABOUT+1); - UT_BROWSER_CB(browser, 0); + ui_browser_cb(browser, 0); return Fl::run(); } |
