summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2023-02-23 15:42:05 +0100
committerGitHub <noreply@github.com>2023-02-23 15:42:05 +0100
commit9f87af8ad9a3d8112518b6bbb5b6075881a5b9a2 (patch)
treefe96512734abb7b29bc6b8fac9816d7cc0c4ae3f /test
parent92818939267fc7fbb6a33d86fb78576f55ce6aca (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')
-rw-r--r--test/CMakeLists.txt1
-rw-r--r--test/Makefile3
-rw-r--r--test/ask.cxx9
-rw-r--r--test/unittest_core.cxx253
-rw-r--r--test/unittests.cxx319
-rw-r--r--test/unittests.h174
6 files changed, 747 insertions, 12 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 67c36fb00..b6bc2c303 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -154,6 +154,7 @@ SET (UNITTEST_SRCS
unittests.h
unittest_about.cxx
unittest_points.cxx
+ unittest_core.cxx
unittest_complex_shapes.cxx
unittest_fast_shapes.cxx
unittest_circles.cxx
diff --git a/test/Makefile b/test/Makefile
index 193cd9d17..2a5ea0980 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -30,7 +30,8 @@ CPPUNITTEST = \
unittest_viewport.cxx \
unittest_scrollbarsize.cxx \
unittest_schemes.cxx \
- unittest_simple_terminal.cxx
+ unittest_simple_terminal.cxx \
+ unittest_core.cxx
OBJUNITTEST = \
unittests.o \
diff --git a/test/ask.cxx b/test/ask.cxx
index 0c2365149..973a42d11 100644
--- a/test/ask.cxx
+++ b/test/ask.cxx
@@ -34,16 +34,17 @@
void rename_button(Fl_Widget *o, void *v) {
int what = fl_int(v);
+ int ret = 0;
Fl_String input;
if (what == 0) {
fl_message_icon_label("§");
- input = fl_input_str(0, "Input (no size limit, use ctrl/j for newline):", o->label());
+ input = fl_input_str(ret, 0, "Input (no size limit, use ctrl/j for newline):", o->label());
} else {
fl_message_icon_label("€");
- input = fl_password_str(20, "Enter password (max. 20 characters):", o->label());
+ input = fl_password_str(ret, 20, "Enter password (max. 20 characters):", o->label());
}
- if (input.value()) {
- o->copy_label(input.value());
+ if (ret == 0) {
+ o->copy_label(input.c_str());
o->redraw();
}
}
diff --git a/test/unittest_core.cxx b/test/unittest_core.cxx
new file mode 100644
index 000000000..7e6699956
--- /dev/null
+++ b/test/unittest_core.cxx
@@ -0,0 +1,253 @@
+//
+// Unit tests for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2023 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file. If this
+// file is missing or damaged, see the license at:
+//
+// https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+// https://www.fltk.org/bugs.php
+//
+
+#include "unittests.h"
+
+#include <FL/Fl_Group.H>
+#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_String.H>
+
+/* Test Fl_String constructor and assignment. */
+TEST(Fl_String, Assignment) {
+ Fl_String null;
+ EXPECT_STREQ(null.c_str(), ""); // default initialisation is an empty string
+ EXPECT_TRUE(null.empty());
+
+ Fl_String null2(NULL);
+ EXPECT_STREQ(null2.c_str(), ""); // initialise with a NULL pointer gets an empty string
+ EXPECT_TRUE(null2.empty());
+
+ Fl_String empty("");
+ EXPECT_STREQ(empty.c_str(), ""); // also, empty CString make empty Fl_String
+ EXPECT_TRUE(empty.empty());
+
+ Fl_String text("hello");
+ EXPECT_STREQ(text.c_str(), "hello"); // Load some text from a CString
+ EXPECT_EQ(text.size(), 5); // did we get the size right?
+ EXPECT_EQ(text.strlen(), 5); // do we have a trailing 0
+ EXPECT_GE(text.capacity(), 5); // do we have the capacity
+ EXPECT_TRUE(!text.empty()); // test the empty() method
+
+ Fl_String text2("abcdef", 3);
+ EXPECT_STREQ(text2.c_str(), "abc");
+ EXPECT_EQ(text2.size(), 3);
+
+ Fl_String text3("abc\0def", 7);
+ EXPECT_EQ(text3.strlen(), 3);
+ EXPECT_EQ(text3.size(), 7);
+
+ Fl_String text4(text);
+ EXPECT_STREQ(text4.c_str(), "hello");
+
+ Fl_String text5 = text;
+ EXPECT_STREQ(text5.c_str(), "hello");
+
+ Fl_String text6 = "yoohoo";
+ EXPECT_STREQ(text6.c_str(), "yoohoo");
+
+ return true;
+}
+
+/* Test methods that access Fl_String content and parts of it. */
+TEST(Fl_String, Access) {
+ Fl_String hello = "hello";
+ EXPECT_STREQ(hello.c_str(), "hello");
+ EXPECT_STREQ(hello.data(), "hello");
+ EXPECT_EQ(hello[1], 'e');
+ EXPECT_EQ(hello[hello.size()], 0);
+ EXPECT_EQ(hello.at(1), 'e');
+ EXPECT_EQ(hello.at(-1), 0);
+ EXPECT_EQ(hello.at(11), 0);
+
+ hello[1] = 'a';
+ EXPECT_STREQ(hello.c_str(), "hallo");
+
+ hello.data()[1] = 'e';
+ EXPECT_STREQ(hello.c_str(), "hello");
+
+ return true;
+}
+
+/* Test the Fl_String capacity management. */
+TEST(Fl_String, Capacity) {
+ Fl_String hello;
+ EXPECT_EQ(hello.capacity(), 0);
+
+ hello = "hi";
+ EXPECT_STREQ(hello.c_str(), "hi");
+ EXPECT_GE(hello.capacity(), 2);
+
+ hello = "the quick brown fox jumps over the lazy dog";
+ EXPECT_STREQ(hello.c_str(), "the quick brown fox jumps over the lazy dog");
+ EXPECT_GE(hello.capacity(), 41);
+
+ int c = hello.capacity();
+ hello.reserve(c+100);
+ EXPECT_STREQ(hello.c_str(), "the quick brown fox jumps over the lazy dog");
+ EXPECT_GE(hello.capacity(), 141);
+
+ hello = "hi";
+ hello.shrink_to_fit();
+ EXPECT_EQ(hello.capacity(), 2);
+
+ return true;
+}
+
+/* Test all methods that operate on Fl_String. */
+TEST(Fl_String, Operations) {
+ Fl_String empty;
+ Fl_String hello = "Hello", world = "World";
+ hello.resize(4);
+ EXPECT_STREQ(hello.c_str(), "Hell");
+
+ hello.clear();
+ EXPECT_TRUE(hello.empty());
+
+ hello = "Hello";
+ hello.insert(3, "-");
+ EXPECT_STREQ(hello.c_str(), "Hel-lo");
+ hello = "Hello";
+ hello.erase(2, 2);
+ EXPECT_STREQ(hello.c_str(), "Heo");
+
+ hello = "Hello";
+ hello.push_back('!');
+ EXPECT_STREQ(hello.c_str(), "Hello!");
+ hello.pop_back();
+ EXPECT_STREQ(hello.c_str(), "Hello");
+ hello.append(world);
+ EXPECT_STREQ(hello.c_str(), "HelloWorld");
+ hello.append("!");
+ EXPECT_STREQ(hello.c_str(), "HelloWorld!");
+ hello = "Hello";
+ hello += world;
+ EXPECT_STREQ(hello.c_str(), "HelloWorld");
+ hello += "!";
+ EXPECT_STREQ(hello.c_str(), "HelloWorld!");
+ hello += '?';
+ EXPECT_STREQ(hello.c_str(), "HelloWorld!?");
+
+ hello = "Hello";
+ hello.replace(0, 0, "Say ", 4);
+ EXPECT_STREQ(hello.c_str(), "Say Hello");
+ hello.replace(0, 4, "");
+ EXPECT_STREQ(hello.c_str(), "Hello");
+ hello.replace(2, 2, "bb");
+ EXPECT_STREQ(hello.c_str(), "Hebbo");
+ hello.replace(2, 2, "xxx");
+ EXPECT_STREQ(hello.c_str(), "Hexxxo");
+ hello.replace(2, 3, "ll");
+ EXPECT_STREQ(hello.c_str(), "Hello");
+ hello.replace(2, 0, NULL, 0);
+ EXPECT_STREQ(hello.c_str(), "Hello");
+ hello.replace(Fl_String::npos, Fl_String::npos, world);
+ EXPECT_STREQ(hello.c_str(), "HelloWorld");
+
+ hello = "Hello";
+ Fl_String sub = hello.substr();
+ EXPECT_STREQ(sub.c_str(), "Hello"); // check correct usage
+ sub = hello.substr(2);
+ EXPECT_STREQ(sub.c_str(), "llo");
+ sub = hello.substr(2, 2);
+ EXPECT_STREQ(sub.c_str(), "ll");
+ sub = hello.substr(-1, 2);
+ EXPECT_TRUE(sub.empty()); // check faulty values
+ sub = hello.substr(20, 2);
+ EXPECT_TRUE(sub.empty());
+ sub = empty.substr(0, 2);
+ EXPECT_TRUE(sub.empty());
+
+ return true;
+}
+
+/* Test all Fl_String functions that are no part of the class. */
+TEST(Fl_String, Non-Member Functions) {
+ Fl_String a = "a", b = "b", empty = "", result;
+ result = a + b;
+ EXPECT_STREQ(result.c_str(), "ab");
+ result = a + empty;
+ EXPECT_STREQ(result.c_str(), "a");
+ result = a + "c";
+ EXPECT_STREQ(result.c_str(), "ac");
+ result = empty + "x";
+ EXPECT_STREQ(result.c_str(), "x");
+ EXPECT_TRUE(!(a == b));
+ EXPECT_TRUE(a == a);
+ EXPECT_TRUE(empty == empty);
+ EXPECT_TRUE(a+b == "ab");
+ EXPECT_TRUE(a+"b" == "a" + b);
+
+ return true;
+}
+
+//
+//------- test aspects of the FLTK core library ----------
+//
+
+/*
+ Create a tab with only a terminal window in it. When shown for the first time,
+ unittest will visualize progress by runing all regsitered tests one-by-one
+ every few miliseconds.
+
+ When run in command line mode (option `--core`), all tests are executed
+ at full speed.
+ */
+class Ut_Core_Test : public Fl_Group {
+
+ Fl_Simple_Terminal *tty;
+ bool suite_ran_;
+
+public:
+
+ // Create the tab
+ static Fl_Widget *create() {
+ return new Ut_Core_Test(UT_TESTAREA_X, UT_TESTAREA_Y, UT_TESTAREA_W, UT_TESTAREA_H);
+ }
+
+ // Constructor for this tab
+ Ut_Core_Test(int x, int y, int w, int h)
+ : Fl_Group(x, y, w, h),
+ tty(NULL),
+ suite_ran_(false)
+ {
+ tty = new Fl_Simple_Terminal(x+4, y+4, w-8, h-8, "Unittest Log");
+ tty->ansi(true);
+ end();
+ Ut_Suite::tty = tty;
+ }
+
+ // Run one single test and repeat calling this until all tests are done
+ static void timer_cb(void*) {
+ // Run a test every few miliseconds to visualize the progress
+ if (Ut_Suite::run_next_test())
+ Fl::repeat_timeout(0.2, timer_cb);
+ }
+
+ // Showing this tab for the first time will trigger the tests
+ void show() FL_OVERRIDE {
+ Fl_Group::show();
+ if (!suite_ran_) {
+ Fl::add_timeout(0.5, timer_cb);
+ suite_ran_ = true;
+ }
+ }
+};
+
+// Register this tab with the unittest app.
+UnitTest core(UT_TEST_CORE, "Core Functionality", Ut_Core_Test::create);
+
+
+
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();
}
diff --git a/test/unittests.h b/test/unittests.h
index 8d04f15ca..cee1dfc3f 100644
--- a/test/unittests.h
+++ b/test/unittests.h
@@ -20,6 +20,10 @@
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
+#include <stdarg.h>
+
+class Fl_Simple_Terminal;
+
// WINDOW/WIDGET SIZES
const int UT_MAINWIN_W = 700; // main window w()
const int UT_MAINWIN_H = 400; // main window h()
@@ -50,7 +54,8 @@ enum {
UT_TEST_VIEWPORT,
UT_TEST_SCROLLBARSIZE,
UT_TEST_SCHEMES,
- UT_TEST_SIMPLE_TERMINAL
+ UT_TEST_SIMPLE_TERMINAL,
+ UT_TEST_CORE,
};
// This class helps to automatically register a new test with the unittest app.
@@ -74,6 +79,173 @@ private:
static UnitTest* test_list_[];
};
+// The following classes and macros implement a subset of the Google Test API
+// without creating any external dependencies.
+//
+// There is nothing to initialise or set up. Just by including these classes,
+// we can create tests anywhere inside the app by simply writing:
+//
+// TEST(Math, Addition) {
+// EXPECT_EQ(3+3, 6);
+// return true;
+// }
+// TEST(Math, Multiplication) {
+// EXPECT_EQ(3*3, 9);
+// return true;
+// }
+// RUN_ALL_TESTS();
+//
+// The test suite must only be run once.
+
+typedef bool (*Ut_Test_Call)();
+
+/**
+ Implement a single test which can in turn contain many EXPECT_* macros.
+ Ut_Test classes are automatically created using the TEST(suite_name, test_name)
+ macro. Tests with identical suite names are grouped into a single suite.
+ */
+class Ut_Test {
+ friend class Ut_Suite;
+ const char *name_;
+ Ut_Test_Call call_;
+ bool failed_;
+ bool done_;
+public:
+ Ut_Test(const char *suitename, const char *testname, Ut_Test_Call call);
+ bool run(const char *suite);
+ void print_failed(const char *suite);
+};
+
+/**
+ Implement test registry and the grouping of tests into a suite. This class
+ holds a number of static elements that register an arbitrary number of tests
+ and groups them into suites via the TEST() macro.
+ */
+class Ut_Suite {
+ static Ut_Suite **suite_list_;
+ static int suite_list_size_;
+ static int num_tests_;
+ static int num_passed_;
+ static int num_failed_;
+
+ Ut_Test **test_list_;
+ int test_list_size_;
+ const char *name_;
+ bool done_;
+ Ut_Suite(const char *name);
+public:
+ void add(Ut_Test *test);
+ int size() { return test_list_size_; }
+ int run();
+ void print_suite_epilog();
+ void print_failed();
+ static Ut_Suite *locate(const char *name);
+ static int run_all_tests();
+ static bool run_next_test();
+ static void printf(const char *format, ...);
+ static void log_bool(const char *file, int line, const char *cond, bool result, bool expected);
+ static void log_string(const char *file, int line, const char *cond, const char *result, const char *expected);
+ static void log_int(const char *file, int line, const char *cond, int result, const char *expected);
+ static void print_prolog();
+ static void print_epilog();
+ static void color(int);
+ static int failed() { return num_failed_; }
+ static const char *red;
+ static const char *green;
+ static const char *normal;
+ static Fl_Simple_Terminal *tty;
+};
+
+#define UT_CONCAT_(prefix, suffix) prefix##suffix
+#define UT_CONCAT(prefix, suffix) UT_CONCAT_(prefix, suffix)
+
+/** Create a test function and register it with the test suites.
+ \param[in] SUITE naming of the test suite for grouping
+ \param[in] CASE name this test
+ */
+#define TEST(SUITE, CASE) \
+ static bool UT_CONCAT(test_call_, __LINE__)(); \
+ Ut_Test UT_CONCAT(test__, __LINE__)(#SUITE, #CASE, UT_CONCAT(test_call_, __LINE__)); \
+ static bool UT_CONCAT(test_call_, __LINE__)()
+
+/** Create a test case where the result is expected to be a boolena with the value true */
+#define EXPECT_TRUE(COND) \
+ bool UT_CONCAT(cond, __LINE__) = COND; \
+ if (UT_CONCAT(cond, __LINE__) != true) { \
+ Ut_Suite::log_bool(__FILE__, __LINE__, #COND, UT_CONCAT(cond, __LINE__), true); \
+ return false; \
+ }
+
+/** Create a test case for string comparison. NULL is ok for both arguments. */
+#define EXPECT_STREQ(A, B) \
+ const char *UT_CONCAT(a, __LINE__) = A; \
+ const char *UT_CONCAT(b, __LINE__) = B; \
+ if ( (UT_CONCAT(a, __LINE__)==NULL && UT_CONCAT(b, __LINE__)!=NULL) \
+ || (UT_CONCAT(a, __LINE__)!=NULL && UT_CONCAT(b, __LINE__)==NULL) \
+ || (UT_CONCAT(b, __LINE__)!=NULL && strcmp(UT_CONCAT(a, __LINE__), UT_CONCAT(b, __LINE__))!=0) ) { \
+ Ut_Suite::log_string(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
+ return false; \
+ }
+
+/** Create a test case for integer comparison. */
+#define EXPECT_EQ(A, B) \
+ int UT_CONCAT(a, __LINE__) = A; \
+ int UT_CONCAT(b, __LINE__) = B; \
+ if (UT_CONCAT(a, __LINE__) != UT_CONCAT(b, __LINE__)) { \
+ Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
+ return false; \
+ }
+
+/** Create a test case for integer comparison. */
+#define EXPECT_NE(A, B) \
+ int UT_CONCAT(a, __LINE__) = A; \
+ int UT_CONCAT(b, __LINE__) = B; \
+ if (UT_CONCAT(a, __LINE__) == UT_CONCAT(b, __LINE__)) { \
+ Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
+ return false; \
+ }
+
+/** Create a test case for integer comparison. */
+#define EXPECT_LT(A, B) \
+ int UT_CONCAT(a, __LINE__) = A; \
+ int UT_CONCAT(b, __LINE__) = B; \
+ if (UT_CONCAT(a, __LINE__) >= UT_CONCAT(b, __LINE__)) { \
+ Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
+ return false; \
+ }
+
+/** Create a test case for integer comparison. */
+#define EXPECT_LE(A, B) \
+ int UT_CONCAT(a, __LINE__) = A; \
+ int UT_CONCAT(b, __LINE__) = B; \
+ if (UT_CONCAT(a, __LINE__) > UT_CONCAT(b, __LINE__)) { \
+ Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
+ return false; \
+ }
+
+/** Create a test case for integer comparison. */
+#define EXPECT_GT(A, B) \
+ int UT_CONCAT(a, __LINE__) = A; \
+ int UT_CONCAT(b, __LINE__) = B; \
+ if (UT_CONCAT(a, __LINE__) <= UT_CONCAT(b, __LINE__)) { \
+ Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
+ return false; \
+ }
+
+/** Create a test case for integer comparison. */
+#define EXPECT_GE(A, B) \
+ int UT_CONCAT(a, __LINE__) = A; \
+ int UT_CONCAT(b, __LINE__) = B; \
+ if (UT_CONCAT(a, __LINE__) < UT_CONCAT(b, __LINE__)) { \
+ Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
+ return false; \
+ }
+
+/** Run all registered suits and their tests, and return the number of failed tests. */
+#define RUN_ALL_TESTS() \
+ Ut_Suite::run_all_tests()
+
+
// The main window needs an additional drawing feature in order to support
// the viewport alignment test.
class Ut_Main_Window : public Fl_Double_Window {