diff --git a/CMakeLists.txt b/CMakeLists.txt index ac5b820..8a72f66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ set(TEST_SRC_FILES tests/cppunit.cpp tests/conversiontests.cpp tests/iotests.cpp + tests/chronotests.cpp ) # meta data @@ -120,12 +121,14 @@ set_target_properties(${META_PROJECT_NAME} PROPERTIES # add check target if(NOT TARGET check) + set(CMAKE_CTEST_COMMAND ctest -V) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) + enable_testing() endif() add_executable(${META_PROJECT_NAME}_tests EXCLUDE_FROM_ALL ${TEST_HEADER_FILES} ${TEST_SRC_FILES}) target_link_libraries(${META_PROJECT_NAME}_tests c++utilities cppunit) set_target_properties(${META_PROJECT_NAME}_tests PROPERTIES CXX_STANDARD 11) -add_test(NAME ${META_PROJECT_NAME}_tests COMMAND ${META_PROJECT_NAME}_tests) +add_test(NAME ${META_PROJECT_NAME}_cppunit COMMAND ${META_PROJECT_NAME}_tests -p "${CMAKE_CURRENT_SOURCE_DIR}/testfiles") add_dependencies(check ${META_PROJECT_NAME}_tests) # add install target diff --git a/testfiles/some_data b/testfiles/some_data new file mode 100644 index 0000000..3dfd5a7 Binary files /dev/null and b/testfiles/some_data differ diff --git a/tests/chronotests.cpp b/tests/chronotests.cpp new file mode 100644 index 0000000..8d04bd2 --- /dev/null +++ b/tests/chronotests.cpp @@ -0,0 +1,101 @@ +#include "../chrono/datetime.h" +#include "../chrono/timespan.h" +#include "../chrono/period.h" +#include "../conversion/conversionexception.h" + +#include +#include + +#include + +using namespace std; +using namespace ConversionUtilities; +using namespace ChronoUtilities; + +using namespace CPPUNIT_NS; + +class ChronoTests : public TestFixture +{ + CPPUNIT_TEST_SUITE(ChronoTests); + CPPUNIT_TEST(testDateTime); + CPPUNIT_TEST(testTimeSpan); + CPPUNIT_TEST(testOperators); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() {} + void tearDown() {} + + void testDateTime(); + void testTimeSpan(); + void testOperators(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ChronoTests); + +/*! + * \brief Tests most important DateTime features. + */ +void ChronoTests::testDateTime() +{ + // test year(), month(), ... + auto test1 = DateTime::fromDateAndTime(2012, 2, 29, 15, 34, 20, 33.0); + CPPUNIT_ASSERT(test1.year() == 2012); + CPPUNIT_ASSERT(test1.month() == 2); + CPPUNIT_ASSERT(test1.day() == 29); + CPPUNIT_ASSERT(test1.minute() == 34); + CPPUNIT_ASSERT(test1.second() == 20); + CPPUNIT_ASSERT(test1.millisecond() == 33); + CPPUNIT_ASSERT(test1.dayOfWeek() == DayOfWeek::Wednesday); + CPPUNIT_ASSERT(test1.dayOfYear() == (31 + 29)); + CPPUNIT_ASSERT(test1.isLeapYear()); + CPPUNIT_ASSERT(test1.toString(DateTimeOutputFormat::DateTimeAndShortWeekday) == "Wed 2012-02-29 15:34:20.33"); + + // test fromTimeStamp() + auto test2 = DateTime::fromTimeStampGmt(1453840331); + CPPUNIT_ASSERT(test2.toString(DateTimeOutputFormat::DateTimeAndShortWeekday) == "Tue 2016-01-26 20:32:11"); + + // test whether ConversionException() is thrown when invalid values are specified + CPPUNIT_ASSERT_THROW(DateTime::fromDateAndTime(2013, 2, 29, 15, 34, 20, 33), ConversionException); + CPPUNIT_ASSERT_THROW(DateTime::fromDateAndTime(2012, 2, 29, 15, 61, 20, 33), ConversionException); + CPPUNIT_ASSERT_THROW(DateTime::fromDateAndTime(2012, 4, 31, 15, 0, 20, 33), ConversionException); + CPPUNIT_ASSERT_THROW(DateTime::fromDateAndTime(2012, 3, 31, 15, 0, 61, 33), ConversionException); +} + +/*! + * \brief Tests most important TimeSpan features. + */ +void ChronoTests::testTimeSpan() +{ + // test fromString(...), this should also test all other from...() methods and + operator + auto test1 = TimeSpan::fromString("2:34:53:2.5"); + // test days(), hours(), ... + CPPUNIT_ASSERT(test1.days() == 3); + CPPUNIT_ASSERT(test1.hours() == 10); + CPPUNIT_ASSERT(test1.minutes() == 53); + CPPUNIT_ASSERT(test1.seconds() == 2); + CPPUNIT_ASSERT(test1.milliseconds() == 500); + CPPUNIT_ASSERT(test1.totalDays() > 3.0 && test1.totalDays() < 4.0); + CPPUNIT_ASSERT(test1.totalHours() > (2 * 24 + 34) && test1.totalHours() < (2 * 24 + 35)); + CPPUNIT_ASSERT(test1.totalMinutes() > (2 * 24 * 60 + 34 * 60 + 53) && test1.totalHours() < (2 * 24 * 60 + 34 * 60 + 54)); + CPPUNIT_ASSERT(test1.toString(TimeSpanOutputFormat::WithMeasures, false) == "3 d 10 h 53 min 2 s 500 ms"); + + // test whether ConversionException() is thrown when invalid values are specified + CPPUNIT_ASSERT_THROW(TimeSpan::fromString("2:34a:53:32.5"), ConversionException); +} + +/*! + * \brief Tests operators of DateTime / TimeSpan. + */ +void ChronoTests::testOperators() +{ + auto dateTime = DateTime::fromDateAndTime(1999, 1, 5, 4, 16); + CPPUNIT_ASSERT((dateTime + TimeSpan::fromDays(2)).day() == 7); + CPPUNIT_ASSERT((dateTime + TimeSpan::fromHours(24)).day() == 6); + CPPUNIT_ASSERT((dateTime + TimeSpan::fromHours(24) + TimeSpan::fromHours(-1)).hour() == 3); + CPPUNIT_ASSERT((dateTime + TimeSpan::fromHours(24) - TimeSpan::fromMinutes(-1)).minute() == 17); + dateTime += TimeSpan::fromDays(365); + CPPUNIT_ASSERT(dateTime.year() == 2000); + CPPUNIT_ASSERT(dateTime.day() == 5); + CPPUNIT_ASSERT(Period(dateTime, dateTime + TimeSpan::fromDays(62)).months() == 2); +} diff --git a/tests/conversiontests.cpp b/tests/conversiontests.cpp index c4817ff..7e4241d 100644 --- a/tests/conversiontests.cpp +++ b/tests/conversiontests.cpp @@ -1,4 +1,5 @@ #include "../conversion/binaryconversion.h" +#include "../conversion/stringconversion.h" #include #include @@ -6,6 +7,7 @@ #include #include #include +#include using namespace std; using namespace ConversionUtilities; @@ -17,6 +19,7 @@ class ConversionTests : public TestFixture CPPUNIT_TEST_SUITE(ConversionTests); CPPUNIT_TEST(testEndianness); CPPUNIT_TEST(testBinaryConversions); + CPPUNIT_TEST(testStringConversions); CPPUNIT_TEST_SUITE_END(); public: @@ -27,6 +30,7 @@ public: void testEndianness(); void testBinaryConversions(); + void testStringConversions(); private: template @@ -69,8 +73,7 @@ void ConversionTests::testConversion(const char *message, function randomDistSigned(numeric_limits::min()); + uniform_int_distribution randomDistUnsigned(0); + for(byte b = 1; b < 100; ++b) { + auto signedRandom = randomDistSigned(m_randomEngine); + auto unsignedRandom = randomDistUnsigned(m_randomEngine); + for(const auto base : initializer_list{2, 8, 10, 16}) { + auto resultString = stringToNumber(numberToString(unsignedRandom, base), base); + auto resultWideString = stringToNumber(numberToString(unsignedRandom, base), base); + CPPUNIT_ASSERT(resultString == unsignedRandom); + CPPUNIT_ASSERT(resultWideString == unsignedRandom); + } + for(const auto base : initializer_list{10}) { + auto resultString = stringToNumber(numberToString(signedRandom, base), base); + auto resultWideString = stringToNumber(numberToString(signedRandom, base), base); + CPPUNIT_ASSERT(resultString == signedRandom); + CPPUNIT_ASSERT(resultWideString == signedRandom); + } } + // test interpretIntegerAsString() + CPPUNIT_ASSERT(interpretIntegerAsString(0x54455354) == "TEST"); + + // test splitString() / joinStrings() + auto splitJoinTest = joinStrings(splitString >(",a,,ab,ABC,s", ",", EmptyPartsTreat::Keep), " ", false, "(", ")"); + CPPUNIT_ASSERT(splitJoinTest == "() (a) () (ab) (ABC) (s)"); + splitJoinTest = joinStrings(splitString >(",a,,ab,ABC,s", ",", EmptyPartsTreat::Keep), " ", true, "(", ")"); + CPPUNIT_ASSERT(splitJoinTest == "(a) (ab) (ABC) (s)"); + splitJoinTest = joinStrings(splitString >(",a,,ab,ABC,s", ",", EmptyPartsTreat::Omit), " ", false, "(", ")"); + CPPUNIT_ASSERT(splitJoinTest == "(a) (ab) (ABC) (s)"); + splitJoinTest = joinStrings(splitString >(",a,,ab,ABC,s", ",", EmptyPartsTreat::Merge), " ", false, "(", ")"); + CPPUNIT_ASSERT(splitJoinTest == "(a,ab) (ABC) (s)"); + + // test findAndReplace() + string findReplaceTest("findAndReplace()"); + findAndReplace(findReplaceTest, "And", "Or"); + CPPUNIT_ASSERT(findReplaceTest == "findOrReplace()"); + + // test startsWith() + CPPUNIT_ASSERT(!startsWith(findReplaceTest, "findAnd")); + CPPUNIT_ASSERT(startsWith(findReplaceTest, "findOr")); + + // test encodeBase64() / decodeBase64() with random data + uniform_int_distribution randomDistChar; + byte originalBase64Data[4047]; + for(byte &c : originalBase64Data) { + c = randomDistChar(m_randomEngine); + } + const auto encodedBase64Data = encodeBase64(originalBase64Data, sizeof(originalBase64Data)); + auto decodedBase64Data = decodeBase64(encodedBase64Data.data(), encodedBase64Data.size()); + CPPUNIT_ASSERT(decodedBase64Data.second == sizeof(originalBase64Data)); + for(unsigned int i = 0; i < sizeof(originalBase64Data); ++i) { + CPPUNIT_ASSERT(decodedBase64Data.first[i] == originalBase64Data[i]); + } } diff --git a/tests/cppunit.cpp b/tests/cppunit.cpp index b23f727..beeae4c 100644 --- a/tests/cppunit.cpp +++ b/tests/cppunit.cpp @@ -1,13 +1,49 @@ +#include "../application/argumentparser.h" +#include "../application/failure.h" + #include #include +#include + using namespace std; +using namespace ApplicationUtilities; using namespace CPPUNIT_NS; -int main(int , char **) -{ - TextUi::TestRunner runner; - TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry(); - runner.addTest(registry.makeTest()); - return !runner.run(string(), false); +namespace UnitTests { + +string testFilesPath("tests"); + +} + +int main(int argc, char **argv) +{ + // setup argument parser + ArgumentParser parser; + HelpArgument helpArg(parser); + Argument testFilesPathArg("test-files-path", "p", "specifies the path to the directory with test files"); + testFilesPathArg.setRequiredValueCount(1); + testFilesPathArg.setValueNames({"path"}); + testFilesPathArg.setCombinable(true); + parser.setMainArguments({&testFilesPathArg, &helpArg}); + + try { + // parse arguments + parser.parseArgs(argc, argv); + if(testFilesPathArg.isPresent()) { + UnitTests::testFilesPath = testFilesPathArg.values().front(); + } + cerr << "Direcoty for test files: " << UnitTests::testFilesPath << endl; + + // run tests + TextUi::TestRunner runner; + TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry(); + runner.addTest(registry.makeTest()); + return !runner.run(string(), false); + } catch(const Failure &failure) { + cerr << "Invalid arguments specified: " << failure.what() << endl; + return -1; + } + + } diff --git a/tests/iotests.cpp b/tests/iotests.cpp index 3520494..5e321ff 100644 --- a/tests/iotests.cpp +++ b/tests/iotests.cpp @@ -1,25 +1,46 @@ +#include "../io/binaryreader.h" +#include "../io/binarywriter.h" + #include #include +#include +#include + using namespace std; +using namespace IoUtilities; using namespace CPPUNIT_NS; +namespace UnitTests { +extern string testFilesPath; +} + class IoTests : public TestFixture { CPPUNIT_TEST_SUITE(IoTests); CPPUNIT_TEST(testFailure); + CPPUNIT_TEST(testBinaryReader); + CPPUNIT_TEST(testBinaryWriter); CPPUNIT_TEST_SUITE_END(); public: - void setUp() {} - void tearDown() {} + void setUp(); + void tearDown(); void testFailure(); + void testBinaryReader(); + void testBinaryWriter(); }; CPPUNIT_TEST_SUITE_REGISTRATION(IoTests); +void IoTests::setUp() +{} + +void IoTests::tearDown() +{} + /*! * \brief Tests for GCC Bug 66145. * \sa https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145 @@ -30,3 +51,117 @@ void IoTests::testFailure() stream.exceptions(ios_base::failbit | ios_base::badbit); CPPUNIT_ASSERT_THROW(stream.open("path/to/file/which/does/not/exist", ios_base::in), ios_base::failure); } + +/*! + * \brief Tests the most important methods of the BinaryReader. + */ +void IoTests::testBinaryReader() +{ + // read test file + fstream testFile; + testFile.open(UnitTests::testFilesPath + "/some_data", ios_base::in | ios_base::binary); + BinaryReader reader(&testFile); + CPPUNIT_ASSERT(reader.readUInt16LE() == 0x0102u); + CPPUNIT_ASSERT(reader.readUInt16BE() == 0x0102u); + CPPUNIT_ASSERT(reader.readUInt24LE() == 0x010203u); + CPPUNIT_ASSERT(reader.readUInt24BE() == 0x010203u); + CPPUNIT_ASSERT(reader.readUInt32LE() == 0x01020304u); + CPPUNIT_ASSERT(reader.readUInt32BE() == 0x01020304u); + CPPUNIT_ASSERT(reader.readUInt40LE() == 0x0102030405u); + CPPUNIT_ASSERT(reader.readUInt40BE() == 0x0102030405u); + CPPUNIT_ASSERT(reader.readUInt56LE() == 0x01020304050607u); + CPPUNIT_ASSERT(reader.readUInt56BE() == 0x01020304050607u); + CPPUNIT_ASSERT(reader.readUInt64LE() == 0x0102030405060708u); + CPPUNIT_ASSERT(reader.readUInt64BE() == 0x0102030405060708u); + testFile.seekg(0); + CPPUNIT_ASSERT(reader.readInt16LE() == 0x0102); + CPPUNIT_ASSERT(reader.readInt16BE() == 0x0102); + CPPUNIT_ASSERT(reader.readInt24LE() == 0x010203); + CPPUNIT_ASSERT(reader.readInt24BE() == 0x010203); + CPPUNIT_ASSERT(reader.readInt32LE() == 0x01020304); + CPPUNIT_ASSERT(reader.readInt32BE() == 0x01020304); + CPPUNIT_ASSERT(reader.readInt40LE() == 0x0102030405); + CPPUNIT_ASSERT(reader.readInt40BE() == 0x0102030405); + CPPUNIT_ASSERT(reader.readInt56LE() == 0x01020304050607); + CPPUNIT_ASSERT(reader.readInt56BE() == 0x01020304050607); + CPPUNIT_ASSERT(reader.readInt64LE() == 0x0102030405060708); + CPPUNIT_ASSERT(reader.readInt64BE() == 0x0102030405060708); + CPPUNIT_ASSERT(reader.readFloat32LE() == 1.125); + CPPUNIT_ASSERT(reader.readFloat64LE() == 1.625); + CPPUNIT_ASSERT(reader.readFloat32BE() == 1.125); + CPPUNIT_ASSERT(reader.readFloat64BE() == 1.625); + CPPUNIT_ASSERT(reader.readBool() == false); + CPPUNIT_ASSERT(reader.readBool() == true); + CPPUNIT_ASSERT(reader.readString(3) == "abc"); + CPPUNIT_ASSERT(reader.readLengthPrefixedString() == "ABC"); + CPPUNIT_ASSERT(reader.readTerminatedString() == "def"); +} + +/*! + * \brief Tests the most important methods of the BinaryWriter. + */ +void IoTests::testBinaryWriter() +{ + // prepare reading expected data + fstream testFile; + testFile.open(UnitTests::testFilesPath + "/some_data", ios_base::in | ios_base::binary); + + // prepare output stream + stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary); + outputStream.exceptions(ios_base::failbit | ios_base::badbit); + char testData[95]; + outputStream.rdbuf()->pubsetbuf(testData, sizeof(testData)); + + // write test data + BinaryWriter writer(&outputStream); + writer.writeUInt16LE(0x0102u); + writer.writeUInt16BE(0x0102u); + writer.writeUInt24LE(0x010203u); + writer.writeUInt24BE(0x010203u); + writer.writeUInt32LE(0x01020304u); + writer.writeUInt32BE(0x01020304u); + writer.writeUInt40LE(0x0102030405u); + writer.writeUInt40BE(0x0102030405u); + writer.writeUInt56LE(0x01020304050607u); + writer.writeUInt56BE(0x01020304050607u); + writer.writeUInt64LE(0x0102030405060708u); + writer.writeUInt64BE(0x0102030405060708u); + + // test written values + for(char c : testData) { + CPPUNIT_ASSERT(c == static_cast(testFile.get())); + if(testFile.tellg() >= 58) { + break; + } + } + testFile.seekg(0); + outputStream.seekp(0); + + // write more test data + writer.writeInt16LE(0x0102); + writer.writeInt16BE(0x0102); + writer.writeInt24LE(0x010203); + writer.writeInt24BE(0x010203); + writer.writeInt32LE(0x01020304); + writer.writeInt32BE(0x01020304); + writer.writeInt40LE(0x0102030405); + writer.writeInt40BE(0x0102030405); + writer.writeInt56LE(0x01020304050607); + writer.writeInt56BE(0x01020304050607); + writer.writeInt64LE(0x0102030405060708); + writer.writeInt64BE(0x0102030405060708); + writer.writeFloat32LE(1.125); + writer.writeFloat64LE(1.625); + writer.writeFloat32BE(1.125); + writer.writeFloat64BE(1.625); + writer.writeBool(false); + writer.writeBool(true); + writer.writeString("abc"); + writer.writeLengthPrefixedString("ABC"); + writer.writeTerminatedString("def"); + + // test written values + for(char c : testData) { + CPPUNIT_ASSERT(c == static_cast(testFile.get())); + } +}