summaryrefslogtreecommitdiff
path: root/test/unittests.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'test/unittests.cxx')
-rw-r--r--test/unittests.cxx319
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();
}