diff --git a/CMakeLists.txt b/CMakeLists.txt index f3dceee..3248a52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,7 @@ set(TEST_SRC_FILES tests/overallmp3.cpp tests/overallogg.cpp tests/overallflac.cpp + tests/tagvalue.cpp ) set(DOC_FILES @@ -169,7 +170,7 @@ set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_DESCRIPTION "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags") set(META_VERSION_MAJOR 6) set(META_VERSION_MINOR 3) -set(META_VERSION_PATCH 0) +set(META_VERSION_PATCH 1) set(META_PUBLIC_SHARED_LIB_DEPENDS c++utilities) set(META_PUBLIC_STATIC_LIB_DEPENDS c++utilities_static) set(META_PRIVATE_COMPILE_DEFINITIONS LEGACY_API) diff --git a/tests/helper.h b/tests/helper.h index c924a54..3074197 100644 --- a/tests/helper.h +++ b/tests/helper.h @@ -15,4 +15,13 @@ inline std::ostream &operator <<(std::ostream &os, const Media::TagValue &tagVal return os << tagValue.toString(Media::TagTextEncoding::Utf8) << " (encoding: " << tagValue.dataEncoding() << ")"; } +/*! + * \brief Prints a PositionInSet to enable CPPUNIT_ASSERT_EQUAL for tag values. + */ +inline std::ostream &operator <<(std::ostream &os, const Media::PositionInSet &pos) +{ + return os << pos.toString(); +} + + #endif // TAGPARSER_TEST_HELPER diff --git a/tests/tagvalue.cpp b/tests/tagvalue.cpp new file mode 100644 index 0000000..3f61b87 --- /dev/null +++ b/tests/tagvalue.cpp @@ -0,0 +1,194 @@ +#include "./helper.h" + +#include "../tagvalue.h" +#include "../id3/id3genres.h" + +#include +#include + +#include +#include + +using namespace std; +using namespace Media; +using namespace ConversionUtilities; +using namespace ChronoUtilities; + +using namespace CPPUNIT_NS; + +class TagValueTests : public TestFixture { + CPPUNIT_TEST_SUITE(TagValueTests); + CPPUNIT_TEST(testBasics); + CPPUNIT_TEST(testBinary); + CPPUNIT_TEST(testInteger); + CPPUNIT_TEST(testPositionInSet); + CPPUNIT_TEST(testTimeSpan); + CPPUNIT_TEST(testDateTime); + CPPUNIT_TEST(testString); + CPPUNIT_TEST(testEqualityOperator); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void testBasics(); + void testBinary(); + void testInteger(); + void testPositionInSet(); + void testTimeSpan(); + void testDateTime(); + void testString(); + void testEqualityOperator(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TagValueTests); + +void TagValueTests::setUp() +{ +} + +void TagValueTests::tearDown() +{ +} + +void TagValueTests::testBasics() +{ + CPPUNIT_ASSERT(TagValue::empty().isEmpty()); + CPPUNIT_ASSERT_EQUAL(TagDataType::Undefined, TagValue().type()); +} + +void TagValueTests::testBinary() +{ + const TagValue binary("123", 3, TagDataType::Binary); + CPPUNIT_ASSERT_EQUAL(TagDataType::Binary, binary.type()); + CPPUNIT_ASSERT_EQUAL("123"s, string(binary.dataPointer())); + CPPUNIT_ASSERT_THROW(binary.toString(), ConversionException); + CPPUNIT_ASSERT_THROW(binary.toInteger(), ConversionException); + CPPUNIT_ASSERT_THROW(binary.toPositionInSet(), ConversionException); + CPPUNIT_ASSERT_THROW(binary.toStandardGenreIndex(), ConversionException); +} + +void TagValueTests::testInteger() +{ + // positive number + TagValue integer(42); + CPPUNIT_ASSERT(!integer.isEmpty()); + CPPUNIT_ASSERT_EQUAL(TagDataType::Integer, integer.type()); + CPPUNIT_ASSERT_EQUAL(static_cast(42), integer.toInteger()); + CPPUNIT_ASSERT_EQUAL("42"s, integer.toString()); + integer.assignInteger(2); + CPPUNIT_ASSERT_EQUAL("Country"s, string(Id3Genres::stringFromIndex(integer.toStandardGenreIndex()))); + + // negative number + integer.assignInteger(-25); + CPPUNIT_ASSERT_EQUAL("-25"s, integer.toString()); + CPPUNIT_ASSERT_EQUAL(PositionInSet(-25), integer.toPositionInSet()); + CPPUNIT_ASSERT_THROW(integer.toStandardGenreIndex(), ConversionException); + + // zero + integer.assignInteger(0); + CPPUNIT_ASSERT_MESSAGE("explicitely assigned zero not considered empty", !integer.isEmpty()); + CPPUNIT_ASSERT_EQUAL("0"s, integer.toString()); + CPPUNIT_ASSERT_EQUAL(DateTime(), integer.toDateTime()); + CPPUNIT_ASSERT_EQUAL(TimeSpan(), integer.toTimeSpan()); + + // empty value treatet as zero when using to...() methods + integer.clearData(); + CPPUNIT_ASSERT_MESSAGE("cleared vale considered empty", integer.isEmpty()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("only date (but not type) cleared"s, TagDataType::Integer, integer.type()); + CPPUNIT_ASSERT_EQUAL(static_cast(0), integer.toInteger()); + CPPUNIT_ASSERT_EQUAL(string(), integer.toString()); + CPPUNIT_ASSERT_EQUAL(DateTime(), integer.toDateTime()); + CPPUNIT_ASSERT_EQUAL(TimeSpan(), integer.toTimeSpan()); +} + +void TagValueTests::testPositionInSet() +{ + const TagValue test(PositionInSet(4, 23)); + CPPUNIT_ASSERT_EQUAL(PositionInSet(4, 23), test.toPositionInSet()); + CPPUNIT_ASSERT_THROW(test.toInteger(), ConversionException); + CPPUNIT_ASSERT_EQUAL("4/23"s, test.toString()); + CPPUNIT_ASSERT_THROW(test.toStandardGenreIndex(), ConversionException); + CPPUNIT_ASSERT_THROW(test.toDateTime(), ConversionException); + CPPUNIT_ASSERT_THROW(test.toTimeSpan(), ConversionException); +} + +void TagValueTests::testTimeSpan() +{ + const TimeSpan fiveMinutes(TimeSpan::fromMinutes(5)); + TagValue timeSpan; + timeSpan.assignTimeSpan(fiveMinutes); + CPPUNIT_ASSERT_EQUAL(fiveMinutes, timeSpan.toTimeSpan()); + CPPUNIT_ASSERT_EQUAL(fiveMinutes.toString(), timeSpan.toString()); + CPPUNIT_ASSERT_THROW(timeSpan.toInteger(), ConversionException); + CPPUNIT_ASSERT_THROW(timeSpan.toDateTime(), ConversionException); + CPPUNIT_ASSERT_THROW(timeSpan.toPositionInSet(), ConversionException); +} + +void TagValueTests::testDateTime() +{ + const DateTime now(DateTime::now()); + TagValue dateTime; + dateTime.assignDateTime(now); + CPPUNIT_ASSERT_EQUAL(now, dateTime.toDateTime()); + CPPUNIT_ASSERT_EQUAL(now.toString(), dateTime.toString()); + CPPUNIT_ASSERT_THROW(dateTime.toInteger(), ConversionException); + CPPUNIT_ASSERT_THROW(dateTime.toTimeSpan(), ConversionException); + CPPUNIT_ASSERT_THROW(dateTime.toPositionInSet(), ConversionException); +} + +void TagValueTests::testString() +{ + CPPUNIT_ASSERT_EQUAL("15\xe4"s, TagValue("15ä", 4, TagTextEncoding::Utf8).toString(TagTextEncoding::Latin1)); + CPPUNIT_ASSERT_EQUAL("15ä"s, TagValue("15ä", 4, TagTextEncoding::Utf8).toString(TagTextEncoding::Utf8)); + CPPUNIT_ASSERT_EQUAL("\x31\0\x35\0"s, TagValue(15).toString(TagTextEncoding::Utf16LittleEndian)); + CPPUNIT_ASSERT_EQUAL("\0\x31\0\x35"s, TagValue(15).toString(TagTextEncoding::Utf16BigEndian)); + CPPUNIT_ASSERT_EQUAL(15, TagValue("\0\x31\0\x35"s, TagTextEncoding::Utf16BigEndian).toInteger()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("original encoding preserved", + "15ä"s, TagValue("15ä", 4, TagTextEncoding::Utf8).toString(TagTextEncoding::Unspecified)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("original encoding preserved", + "\0\x31\0\x35"s, TagValue("\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian).toString(TagTextEncoding::Unspecified)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("UTF-8 BOM truncated", + "täst"s, TagValue("\xef\xbb\xbftäst", 8, TagTextEncoding::Utf8).toString(TagTextEncoding::Unspecified)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("UTF-16 LE BOM truncated", + "\0t\0\xe4\0s\0t"s, TagValue("\xff\xfe\0t\0\xe4\0s\0t", 10, TagTextEncoding::Utf16LittleEndian).toString(TagTextEncoding::Unspecified)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("UTF-16 BE BOM truncated", + "t\0\xe4\0s\0t\0"s, TagValue("\xfe\xfft\0\xe4\0s\0t\0", 10, TagTextEncoding::Utf16BigEndian).toString(TagTextEncoding::Unspecified)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("conversion via c'tor", + "15\xe4"s, TagValue("\xef\xbb\xbf\x31\x35ä", 7, TagTextEncoding::Utf8, TagTextEncoding::Latin1).toString(TagTextEncoding::Unspecified)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("conversion to int", 15, TagValue("\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian).toInteger()); + CPPUNIT_ASSERT_THROW_MESSAGE("failing conversion to int", TagValue("15ä", 4, TagTextEncoding::Utf8).toInteger(), ConversionException); + CPPUNIT_ASSERT_EQUAL_MESSAGE("conversion to pos", PositionInSet(4, 15), TagValue("4 / 15", 6, TagTextEncoding::Utf8).toPositionInSet()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("conversion to int", PositionInSet(15), TagValue("\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian).toPositionInSet()); + CPPUNIT_ASSERT_THROW_MESSAGE("failing conversion pos", TagValue("a4 / 15", 7, TagTextEncoding::Utf8).toPositionInSet(), ConversionException); + CPPUNIT_ASSERT_EQUAL_MESSAGE("conversion to date", DateTime::fromDate(2004, 4, 15), TagValue("2004-04-15", 10, TagTextEncoding::Utf8).toDateTime()); + CPPUNIT_ASSERT_THROW_MESSAGE("failing conversion to date", TagValue("_", 1, TagTextEncoding::Utf8).toDateTime(), ConversionException); + CPPUNIT_ASSERT_EQUAL_MESSAGE("conversion to time span", TimeSpan::fromHours(1.5), TagValue("01:30:00", 10, TagTextEncoding::Utf8).toTimeSpan()); + CPPUNIT_ASSERT_THROW_MESSAGE("failing conversion to time span", TagValue("_", 1, TagTextEncoding::Utf8).toTimeSpan(), ConversionException); +} + +void TagValueTests::testEqualityOperator() +{ + CPPUNIT_ASSERT_MESSAGE("equality requires identical types or identical string representation"s, + TagValue(0) != TagValue::empty()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("types might differ"s, + TagValue(15), TagValue(15)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("types might differ"s, + TagValue("15", 2, TagTextEncoding::Latin1), TagValue(15)); + CPPUNIT_ASSERT_MESSAGE("encoding must be equal if relevant for types"s, + TagValue("\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian) != TagValue("15", 2, TagTextEncoding::Latin1)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("encoding is ignored when not relevant for types"s, + TagValue("\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian), TagValue(15)); + + // meta-data + TagValue withDescription(15); + withDescription.setDescription("test"); + CPPUNIT_ASSERT_MESSAGE("meta-data must be equal"s, withDescription != TagValue(15)); + TagValue withDescription2(withDescription); + CPPUNIT_ASSERT_EQUAL(withDescription, withDescription2); + withDescription2.setMimeType("foo/bar"); + CPPUNIT_ASSERT(withDescription != withDescription2); + withDescription.setMimeType(withDescription2.mimeType()); + CPPUNIT_ASSERT_EQUAL(withDescription, withDescription2); +}