Add unit tests
authorMikko Rasa <tdb@tdb.fi>
Mon, 25 Jul 2011 14:46:24 +0000 (17:46 +0300)
committerMikko Rasa <tdb@tdb.fi>
Mon, 25 Jul 2011 14:46:24 +0000 (17:46 +0300)
12 files changed:
tests/Build [new file with mode: 0644]
tests/codec.cpp [new file with mode: 0644]
tests/console/console.cpp [new file with mode: 0644]
tests/datetime.cpp [new file with mode: 0644]
tests/file.cpp [new file with mode: 0644]
tests/getopt.cpp [new file with mode: 0644]
tests/maputils.cpp [new file with mode: 0644]
tests/memory.cpp [new file with mode: 0644]
tests/pipe.cpp [new file with mode: 0644]
tests/thread.cpp [new file with mode: 0644]
tests/timezone.cpp [new file with mode: 0644]
tests/variant.cpp [new file with mode: 0644]

diff --git a/tests/Build b/tests/Build
new file mode 100644 (file)
index 0000000..982059e
--- /dev/null
@@ -0,0 +1,15 @@
+package "mspcore-tests"
+{
+       require "mspcore";
+       require "msptest";
+
+       program "test"
+       {
+               source ".";
+       };
+
+       program "consoletest"
+       {
+               source "console/console.cpp";
+       };
+};
diff --git a/tests/codec.cpp b/tests/codec.cpp
new file mode 100644 (file)
index 0000000..896ce5d
--- /dev/null
@@ -0,0 +1,41 @@
+#include <msp/stringcodec/ascii.h>
+#include <msp/stringcodec/utf8.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class CodecTests: public Test::RegisteredTest<CodecTests>
+{
+public:
+       CodecTests();
+
+       static const char *get_name() { return "Codec"; }
+
+private:
+       void invalid_character();
+       void invalid_sequence();
+};
+
+
+CodecTests::CodecTests()
+{
+       add(&CodecTests::invalid_character, "invalid_character").expect_throw<StringCodec::invalid_character>();
+       add(&CodecTests::invalid_sequence, "invalid_sequence").expect_throw<StringCodec::invalid_sequence>();
+}
+
+void CodecTests::invalid_character()
+{
+       StringCodec::unichar ch = 0xE4;
+       StringCodec::Ascii::Encoder enc;
+       string buf;
+       enc.encode_char(ch, buf);
+}
+
+void CodecTests::invalid_sequence()
+{
+       string str = "\343\201";
+       string::const_iterator iter = str.begin();
+       StringCodec::Utf8::Decoder dec;
+       dec.decode_char(str, iter);
+}
diff --git a/tests/console/console.cpp b/tests/console/console.cpp
new file mode 100644 (file)
index 0000000..04ad4ac
--- /dev/null
@@ -0,0 +1,26 @@
+#include <msp/io/console.h>
+#include <msp/io/poll.h>
+#include <msp/io/print.h>
+
+using namespace Msp;
+
+int main()
+{
+       IO::cin.set_local_echo(false);
+       IO::cin.set_line_buffer(false);
+       while(1)
+       {
+               if(IO::poll(IO::cin, IO::P_INPUT)==IO::P_INPUT)
+               {
+                       char c = IO::cin.get();
+                       IO::print("Got character '%c' (%02X)\n", c, c);
+               }
+               else
+               {
+                       IO::print("EOF\n");
+                       break;
+               }
+       }
+
+       return 0;
+}
diff --git a/tests/datetime.cpp b/tests/datetime.cpp
new file mode 100644 (file)
index 0000000..45a8699
--- /dev/null
@@ -0,0 +1,118 @@
+#include <msp/time/datetime.h>
+#include <msp/time/timestamp.h>
+#include <msp/time/timezone.h>
+#include <msp/time/units.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class DateTimeTests: public Test::RegisteredTest<DateTimeTests>
+{
+public:
+       DateTimeTests();
+
+       static const char *get_name() { return "DateTime"; }
+
+private:
+       void add_days();
+       void add_timedelta();
+       void leap_year();
+       void format();
+       void timestamp();
+       void timezone();
+};
+
+
+DateTimeTests::DateTimeTests()
+{
+       add(&DateTimeTests::add_days, "add_days");
+       add(&DateTimeTests::add_timedelta, "+= TimeDelta");
+       add(&DateTimeTests::leap_year, "Leap years");
+       add(&DateTimeTests::format, "Formatting");
+       add(&DateTimeTests::timestamp, "Timestamp conversion");
+       add(&DateTimeTests::timezone, "Timezone conversion");
+}
+
+void DateTimeTests::add_days()
+{
+       Time::DateTime dt(2010, 1, 1);
+       dt.add_days(1);
+       EXPECT_EQUAL(dt, Time::DateTime(2010, 1, 2));
+       dt.add_days(57);
+       EXPECT_EQUAL(dt, Time::DateTime(2010, 2, 28));
+       dt.add_days(307);
+       EXPECT_EQUAL(dt, Time::DateTime(2011, 1, 1));
+       dt.add_days(-366);
+       EXPECT_EQUAL(dt, Time::DateTime(2009, 12, 31));
+}
+
+void DateTimeTests::add_timedelta()
+{
+       Time::DateTime dt(2010, 1, 1, 23, 50, 0);
+       dt += 20*Time::min;
+       EXPECT_EQUAL(dt, Time::DateTime(2010, 1, 2, 0, 10, 0));
+       dt += 23*Time::hour+50*Time::min;
+       EXPECT_EQUAL(dt, Time::DateTime(2010, 1, 3));
+       dt += 52*Time::week;
+       EXPECT_EQUAL(dt, Time::DateTime(2011, 1, 2));
+}
+
+void DateTimeTests::leap_year()
+{
+       Time::DateTime dt(1996, 2, 1);
+       dt.add_days(29);
+       EXPECT_EQUAL(dt, Time::DateTime(1996, 3, 1));
+
+       dt = Time::DateTime(2000, 2, 1);
+       dt.add_days(29);
+       EXPECT_EQUAL(dt, Time::DateTime(2000, 3, 1));
+
+       dt = Time::DateTime(2100, 2, 1);
+       dt.add_days(29);
+       EXPECT_EQUAL(dt, Time::DateTime(2100, 3, 2));
+
+       dt = Time::DateTime(1996, 2, 1);
+       dt.add_days(8*365);
+       EXPECT_EQUAL(dt, Time::DateTime(2004, 1, 30));
+
+       dt = Time::DateTime(2096, 2, 1);
+       dt.add_days(8*365);
+       EXPECT_EQUAL(dt, Time::DateTime(2104, 1, 31));
+
+       dt = Time::DateTime(2010, 2, 1);
+       dt.add_days(400*365+96);
+       EXPECT_EQUAL(dt, Time::DateTime(2410, 1, 31));
+}
+
+void DateTimeTests::format()
+{
+       Time::DateTime dt(2010, 4, 27, 13, 57, 12);
+       EXPECT_EQUAL(dt.format("%Y-%m-%d"), "2010-04-27");
+       EXPECT_EQUAL(dt.format("%d%m%y"), "270410");
+       EXPECT_EQUAL(dt.format("%H:%M:%S"), "13:57:12");
+       EXPECT_EQUAL(dt.format("%I:%M %p"), "01:57 PM");
+       EXPECT_EQUAL(Time::DateTime(2010, 4, 27, 12, 0, 0).format("%p"), "PM");
+       EXPECT_EQUAL(dt.format_rfc3339(), "2010-04-27T13:57:12Z");
+       dt.set_timezone(Time::TimeZone(120));
+       EXPECT_EQUAL(dt.format_rfc3339(), "2010-04-27T13:57:12+02:00");
+}
+
+void DateTimeTests::timestamp()
+{
+       Time::DateTime dt(2010, 1, 1);
+       Time::TimeStamp ts = dt.get_timestamp();
+       Time::DateTime dt2(ts);
+       EXPECT_EQUAL(dt, dt2);
+}
+
+void DateTimeTests::timezone()
+{
+       Time::DateTime dt(2010, 1, 1, 2, 0, 0);
+       dt.convert_timezone(Time::TimeZone(120));
+       EXPECT_EQUAL(dt, Time::DateTime(2010, 1, 1, 4, 0, 0));
+       dt.convert_timezone(Time::TimeZone(-300));
+       EXPECT_EQUAL(dt, Time::DateTime(2009, 12, 31, 21, 0, 0));
+       dt.convert_timezone(Time::TimeZone::utc());
+       EXPECT_EQUAL(dt, Time::DateTime(2010, 1, 1, 2, 0, 0));
+}
diff --git a/tests/file.cpp b/tests/file.cpp
new file mode 100644 (file)
index 0000000..ccd2eb5
--- /dev/null
@@ -0,0 +1,87 @@
+#include <msp/core/systemerror.h>
+#include <msp/io/file.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class FileTests: public Test::RegisteredTest<FileTests>
+{
+public:
+       FileTests();
+
+       static const char *get_name() { return "File"; }
+
+private:
+       void write();
+       void read();
+       void seek();
+       void file_not_found();
+       void invalid_access();
+};
+
+
+FileTests::FileTests()
+{
+       add(&FileTests::write,          "Writing");
+       add(&FileTests::read,           "Reading");
+       add(&FileTests::seek,           "Seeking");
+       add(&FileTests::file_not_found, "file_not_found").expect_throw<IO::file_not_found>();
+       add(&FileTests::invalid_access, "invalid_access").expect_throw<IO::invalid_access>();
+}
+
+void FileTests::write()
+{
+       IO::File out("test.txt", IO::M_WRITE);
+       out.write("foobar\n");
+       out.write("quux\n");
+}
+
+void FileTests::read()
+{
+       IO::File in("test.txt");
+
+       string line;
+       EXPECT(in.getline(line));
+       EXPECT_EQUAL(line, "foobar");
+
+       char buf[128];
+       unsigned len = in.read(buf, sizeof(buf));
+       EXPECT_EQUAL(len, 5U);
+       EXPECT_EQUAL(string(buf, len), "quux\n");
+
+       len = in.read(buf, sizeof(buf));
+       EXPECT_EQUAL(len, 0U);
+       EXPECT(in.eof());
+}
+
+void FileTests::seek()
+{
+       IO::File in("test.txt");
+
+       IO::SeekOffset pos = in.seek(3, IO::S_BEG);
+       EXPECT_EQUAL(pos, 3);
+       char c = in.get();
+       EXPECT_EQUAL(c, 'b');
+
+       pos = in.seek(1, IO::S_CUR);
+       EXPECT_EQUAL(pos, 5);
+       c = in.get();
+       EXPECT_EQUAL(c, 'r');
+
+       pos = in.seek(-2, IO::S_END);
+       EXPECT_EQUAL(pos, 10);
+       c = in.get();
+       EXPECT_EQUAL(c, 'x');
+}
+
+void FileTests::file_not_found()
+{
+       IO::File in("does.not.exist");
+}
+
+void FileTests::invalid_access()
+{
+       IO::File in("test.txt");
+       in.write("foo\n");
+}
diff --git a/tests/getopt.cpp b/tests/getopt.cpp
new file mode 100644 (file)
index 0000000..cb5df62
--- /dev/null
@@ -0,0 +1,141 @@
+#include <msp/core/getopt.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class GetOptTests: public Test::RegisteredTest<GetOptTests>
+{
+public:
+       GetOptTests();
+
+       static const char *get_name() { return "GetOpt"; }
+
+private:
+       void short_options();
+       void long_options();
+       void arguments();
+       void non_options();
+       void help();
+       void invalid_option();
+       void invalid_arg();
+       void missing_arg();
+};
+
+GetOptTests::GetOptTests()
+{
+       add(&GetOptTests::short_options, "Short options");
+       add(&GetOptTests::long_options, "Long options");
+       add(&GetOptTests::arguments, "Option arguments");
+       add(&GetOptTests::non_options, "Non-options");
+       add(&GetOptTests::help, "Help").expect_throw<usage_error>();
+       add(&GetOptTests::invalid_option, "Invalid option").expect_throw<usage_error>();
+       add(&GetOptTests::invalid_arg, "Invalid argument").expect_throw<usage_error>();
+       add(&GetOptTests::missing_arg, "Missing argument").expect_throw<usage_error>();
+}
+
+void GetOptTests::short_options()
+{
+       static const char *argv[] = { "test", "-a", "-bc", 0 };
+
+       bool a = false;
+       bool b = false;
+       bool c = false;
+
+       GetOpt getopt;
+       getopt.add_option('a', "a", a, GetOpt::NO_ARG);
+       getopt.add_option('b', "b", b, GetOpt::NO_ARG);
+       getopt.add_option('c', "c", c, GetOpt::NO_ARG);
+       getopt(3, argv);
+
+       EXPECT(a && b && c);
+}
+
+void GetOptTests::long_options()
+{
+       static const char *argv[] = { "test", "--foo", "--bar", 0 };
+
+       bool foo = false;
+       bool bar = false;
+
+       GetOpt getopt;
+       getopt.add_option("foo", foo, GetOpt::NO_ARG);
+       getopt.add_option("bar", bar, GetOpt::NO_ARG);
+       getopt(3, argv);
+
+       EXPECT(foo && bar);
+}
+
+void GetOptTests::arguments()
+{
+       static const char *argv[] = { "test", "-aabc", "-b", "x y z", "--foo=42", "--bar", "69", 0 };
+
+       string a;
+       string b;
+       int foo = 0;
+       int bar = 0;
+
+       GetOpt getopt;
+       getopt.add_option('a', "a", a, GetOpt::REQUIRED_ARG);
+       getopt.add_option('b', "b", b, GetOpt::REQUIRED_ARG);
+       getopt.add_option("foo", foo, GetOpt::REQUIRED_ARG);
+       getopt.add_option("bar", bar, GetOpt::REQUIRED_ARG);
+       getopt(7, argv);
+
+       EXPECT_EQUAL(a, "abc");
+       EXPECT_EQUAL(b, "x y z");
+       EXPECT_EQUAL(foo, 42);
+       EXPECT_EQUAL(bar, 69);
+}
+
+void GetOptTests::non_options()
+{
+       static const char *argv[] = { "test", "-a", "foo", "-b", "bar", "baz", 0 };
+
+       bool a = false;
+       bool b = false;
+
+       GetOpt getopt;
+       getopt.add_option('a', "a", a, GetOpt::NO_ARG);
+       getopt.add_option('b', "b", b, GetOpt::NO_ARG);
+       getopt(6, argv);
+
+       EXPECT(a && b);
+       EXPECT_EQUAL(getopt.get_args().size(), 3U);
+}
+
+void GetOptTests::help()
+{
+       static const char *argv[] = { "test", "--help", 0 };
+
+       GetOpt getopt;
+       getopt(2, argv);
+}
+
+void GetOptTests::invalid_option()
+{
+       static const char *argv[] = { "test", "--invalid", 0 };
+
+       GetOpt getopt;
+       getopt(2, argv);
+}
+
+void GetOptTests::invalid_arg()
+{
+       static const char *argv[] = { "test", "--intval=foo", 0 };
+
+       int value;
+       GetOpt getopt;
+       getopt.add_option("intval", value, GetOpt::REQUIRED_ARG);
+       getopt(2, argv);
+}
+
+void GetOptTests::missing_arg()
+{
+       static const char *argv[] = { "test", "--value", 0 };
+
+       string value;
+       GetOpt getopt;
+       getopt.add_option("value", value, GetOpt::REQUIRED_ARG);
+       getopt(2, argv);
+}
diff --git a/tests/maputils.cpp b/tests/maputils.cpp
new file mode 100644 (file)
index 0000000..78cdaad
--- /dev/null
@@ -0,0 +1,40 @@
+#include <map>
+#include <msp/core/maputils.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class MapUtilTests: public Test::RegisteredTest<MapUtilTests>
+{
+public:
+       MapUtilTests();
+
+       static const char *get_name() { return "maputils"; }
+
+private:
+       void success();
+       void error();
+};
+
+
+MapUtilTests::MapUtilTests()
+{
+       add(&MapUtilTests::success, "success");
+       add(&MapUtilTests::error, "error").expect_throw<key_error>();
+}
+
+void MapUtilTests::success()
+{
+       map<int, string> m;
+       m[1] = "foo";
+       m[3] = "bar";
+       EXPECT_EQUAL(get_item(m, 1), "foo");
+       EXPECT_EQUAL(get_item(m, 3), "bar");
+}
+
+void MapUtilTests::error()
+{
+       map<int, string> m;
+       get_item(m, 0);
+}
diff --git a/tests/memory.cpp b/tests/memory.cpp
new file mode 100644 (file)
index 0000000..5c3fa69
--- /dev/null
@@ -0,0 +1,73 @@
+#include <msp/io/memory.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class MemoryTests: public Test::RegisteredTest<MemoryTests>
+{
+public:
+       MemoryTests();
+
+       static const char *get_name() { return "Memory"; }
+
+private:
+       void write();
+       void read();
+       void getline();
+       void invalid_access();
+};
+
+
+MemoryTests::MemoryTests()
+{
+       add(&MemoryTests::write,          "write");
+       add(&MemoryTests::read,           "read");
+       add(&MemoryTests::getline,        "getline");
+       add(&MemoryTests::invalid_access, "invalid_access").expect_throw<IO::invalid_access>();
+}
+
+void MemoryTests::write()
+{
+       char buf[64] = { };
+       IO::Memory mem(buf, sizeof(buf));
+       mem.write("foobar");
+       EXPECT(equal(buf, buf+6, "foobar"));
+       for(unsigned i=6; i<sizeof(buf); ++i)
+               if(buf[i]!=0)
+                       fail("Garbage in buffer");
+}
+
+void MemoryTests::read()
+{
+       static const char buf[] = "foobar";
+
+       IO::Memory mem(buf, 6);
+       char rbuf[16];
+       unsigned len = mem.read(rbuf, sizeof(rbuf));
+       EXPECT_EQUAL(len, 6);
+       EXPECT(equal(rbuf, rbuf+6, buf));
+}
+
+void MemoryTests::getline()
+{
+       static const char buf[] = "foobar\n\nquux\n";
+
+       IO::Memory mem(buf, sizeof(buf)-1);
+       string line;
+       EXPECT(mem.getline(line));
+       EXPECT_EQUAL(line, "foobar");
+       EXPECT(mem.getline(line));
+       EXPECT_EQUAL(line, "");
+       EXPECT(mem.getline(line));
+       EXPECT_EQUAL(line, "quux");
+       EXPECT(!mem.getline(line));
+}
+
+void MemoryTests::invalid_access()
+{
+       static const char buf[] = "foobar";
+
+       IO::Memory mem(buf, sizeof(buf));
+       mem.write("quux", 4);
+}
diff --git a/tests/pipe.cpp b/tests/pipe.cpp
new file mode 100644 (file)
index 0000000..0310bdf
--- /dev/null
@@ -0,0 +1,81 @@
+#include <msp/core/thread.h>
+#include <msp/io/pipe.h>
+#include <msp/io/poll.h>
+#include <msp/time/units.h>
+#include <msp/test/test.h>
+
+using namespace Msp;
+
+class PipeTests: public Test::RegisteredTest<PipeTests>
+{
+public:
+       PipeTests();
+
+       static const char *get_name() { return "Pipe"; }
+
+private:
+       void readwrite();
+       void poll();
+};
+
+
+class PipeWriter: public Thread
+{
+private:
+       IO::Pipe &pipe;
+
+public:
+       PipeWriter(IO::Pipe &);
+
+private:
+       virtual void main();
+};
+
+
+PipeTests::PipeTests()
+{
+       add(&PipeTests::readwrite, "Read/write");
+       add(&PipeTests::poll, "Poll");
+}
+
+void PipeTests::readwrite()
+{
+       IO::Pipe pipe;
+       PipeWriter writer(pipe);
+
+       for(unsigned i=0; i<256; ++i)
+       {
+               unsigned char c = pipe.get();
+               if(c!=i)
+                       fail("Invalid data");
+       }
+
+       writer.join();
+}
+
+void PipeTests::poll()
+{
+       IO::Pipe pipe;
+
+       pipe.put(1);
+       IO::PollEvent ev = IO::poll(pipe, IO::P_INPUT);
+       EXPECT_EQUAL(ev, IO::P_INPUT);
+
+       pipe.get();
+
+       ev = IO::poll(pipe, IO::P_INPUT, 100*Time::msec);
+       EXPECT_EQUAL(ev, IO::P_NONE);
+}
+
+
+PipeWriter::PipeWriter(IO::Pipe &p):
+       pipe(p)
+{
+       launch();
+}
+
+void PipeWriter::main()
+{
+       for(unsigned i=0; i<256; ++i)
+               pipe.put(i);
+}
diff --git a/tests/thread.cpp b/tests/thread.cpp
new file mode 100644 (file)
index 0000000..9529af8
--- /dev/null
@@ -0,0 +1,129 @@
+#include <list>
+#include <vector>
+#include <msp/core/thread.h>
+#include <msp/time/units.h>
+#include <msp/time/utils.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class ThreadTests: public Test::RegisteredTest<ThreadTests>
+{
+private:
+       std::vector<int> data;
+
+public:
+       ThreadTests();
+
+       static const char *get_name() { return "Thread"; }
+
+private:
+       void single();
+       void multiple();
+};
+
+
+class TestThread: public Thread
+{
+private:
+       int *start;
+       unsigned count;
+       unsigned step;
+       int value;
+       bool done;
+
+public:
+       TestThread(int *, unsigned, unsigned, int);
+
+private:
+       virtual void main();
+
+public:
+       bool is_done() const { return done; }
+};
+
+
+ThreadTests::ThreadTests():
+       data(1000000)
+{
+       add(&ThreadTests::single, "Single thread");
+       add(&ThreadTests::multiple, "Multiple threads");
+}
+
+void ThreadTests::single()
+{
+       fill(data.begin(), data.end(), -1);
+
+       TestThread thread(&data[0], data.size(), 1, 1);
+       unsigned wait = 100;
+       while(wait && !thread.is_done())
+       {
+               Time::sleep(100*Time::msec);
+               --wait;
+       }
+
+       if(!wait)
+               fail("Thread did not finish");
+
+       thread.join();
+
+       for(vector<int>::iterator i=data.begin(); i!=data.end(); ++i)
+               if(*i!=1)
+                       fail("Invalid data");
+}
+
+void ThreadTests::multiple()
+{
+       fill(data.begin(), data.end(), -1);
+
+       list<TestThread *> threads;
+       for(unsigned i=0; i<10; ++i)
+               threads.push_back(new TestThread(&data[i], data.size()/10, 10, i+1));
+       unsigned wait = 100;
+       while(wait && !threads.empty())
+       {
+               Time::sleep(100*Time::msec);
+               for(list<TestThread *>::iterator i=threads.begin(); i!=threads.end();)
+               {
+                       if((*i)->is_done())
+                       {
+                               (*i)->join();
+                               delete *i;
+                               threads.erase(i++);
+                       }
+                       else
+                               ++i;
+               }
+               --wait;
+       }
+
+       if(!wait)
+               fail("Threads did not finish");
+
+       for(unsigned i=0; i<data.size(); ++i)
+               if(data[i]!=static_cast<int>(i%10+1))
+                       fail("Invalid data");
+}
+
+
+TestThread::TestThread(int *s, unsigned c, unsigned t, int v):
+       start(s),
+       count(c),
+       step(t),
+       value(v),
+       done(false)
+{
+       launch();
+}
+
+void TestThread::main()
+{
+       int *ptr = start;
+       for(unsigned i=0; i<count; ++i)
+       {
+               *ptr = value;
+               ptr += step;
+       }
+       done = true;
+}
diff --git a/tests/timezone.cpp b/tests/timezone.cpp
new file mode 100644 (file)
index 0000000..c59cdd3
--- /dev/null
@@ -0,0 +1,48 @@
+#include <msp/time/units.h>
+#include <msp/time/timezone.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class TimeZoneTests: public Test::RegisteredTest<TimeZoneTests>
+{
+public:
+       TimeZoneTests();
+
+       static const char *get_name() { return "TimeZone"; }
+
+private:
+       void utc();
+       void local();
+       void custom();
+};
+
+
+TimeZoneTests::TimeZoneTests()
+{
+       add(&TimeZoneTests::utc, "UTC");
+       add(&TimeZoneTests::local, "Local");
+       add(&TimeZoneTests::custom, "Custom");
+}
+
+void TimeZoneTests::utc()
+{
+       Time::TimeZone tz = Time::TimeZone::utc();
+       EXPECT_EQUAL(tz.get_name(), "UTC");
+       EXPECT_EQUAL(tz.get_offset(), Time::zero);
+}
+
+void TimeZoneTests::local()
+{
+       Time::TimeZone tz = Time::TimeZone::local();
+       info(format("'%s' %s", tz.get_name(), tz.get_offset()));
+}
+
+void TimeZoneTests::custom()
+{
+       Time::TimeZone tz1(120);
+       EXPECT_EQUAL(tz1.get_name(), "UTC+2");
+       Time::TimeZone tz2(345);
+       EXPECT_EQUAL(tz2.get_name(), "UTC+5:45");
+}
diff --git a/tests/variant.cpp b/tests/variant.cpp
new file mode 100644 (file)
index 0000000..99cacdf
--- /dev/null
@@ -0,0 +1,113 @@
+#include <msp/core/variant.h>
+#include <msp/test/test.h>
+
+using namespace std;
+using namespace Msp;
+
+class VariantTests: public Test::RegisteredTest<VariantTests>
+{
+public:
+       VariantTests();
+
+       static const char *get_name() { return "Variant"; }
+
+private:
+       void integer();
+       void stdstring();
+       void pointer();
+       void ref_into();
+       void copying();
+       void destruction();
+       void types();
+       void mismatch();
+};
+
+class Counter
+{
+private:
+       int &count;
+
+public:
+       Counter(int &c): count(c) { ++count; }
+       Counter(const Counter &c): count(c.count) { ++count; }
+       ~Counter() { --count; }
+};
+
+VariantTests::VariantTests()
+{
+       add(&VariantTests::integer, "Integer");
+       add(&VariantTests::stdstring, "std::string");
+       add(&VariantTests::pointer, "Pointer");
+       add(&VariantTests::ref_into, "Reference into");
+       add(&VariantTests::copying, "Copying");
+       add(&VariantTests::destruction, "Destruction");
+       add(&VariantTests::types, "Type checks");
+       add(&VariantTests::mismatch, "Mismatch").expect_throw<type_mismatch>();
+}
+
+void VariantTests::integer()
+{
+       Variant var = 42;
+       EXPECT_EQUAL(var.value<int>(), 42);
+}
+
+void VariantTests::stdstring()
+{
+       Variant var = string("foobar");
+       EXPECT_EQUAL(var.value<string>(), "foobar");
+}
+
+void VariantTests::pointer()
+{
+       int i = 7;
+       Variant var = &i;
+       *var.value<int *>() = 42;
+       EXPECT_EQUAL(i, 42);
+}
+
+void VariantTests::ref_into()
+{
+       Variant var = 7;
+       int &r = var.value<int>();
+       r = 42;
+       EXPECT_EQUAL(var.value<int>(), 42);
+}
+
+void VariantTests::copying()
+{
+       Variant var = 42;
+       Variant var2 = var;
+       EXPECT_EQUAL(var2.value<int>(), 42);
+}
+
+void VariantTests::destruction()
+{
+       int count = 0;
+       {
+               Variant var = Counter(count);
+               Variant var2 = var;
+               EXPECT_EQUAL(count, 2);
+               Variant var3;
+               var3 = var;
+               EXPECT_EQUAL(count, 3);
+               var2 = Variant();
+               EXPECT_EQUAL(count, 2);
+       }
+       EXPECT_EQUAL(count, 0);
+}
+
+void VariantTests::types()
+{
+       Variant var = 42U;
+       EXPECT(!var.check_type<int>());
+       EXPECT(var.check_type<unsigned>());
+       EXPECT(!var.check_type<float>());
+       EXPECT(!var.check_type<bool>());
+       EXPECT(!var.check_type<string>());
+}
+
+void VariantTests::mismatch()
+{
+       Variant var = 1;
+       var.value<string>();
+}