3#include "../id3/id3genres.h"
4#include "../tagvalue.h"
6#include <c++utilities/chrono/format.h>
7#include <c++utilities/conversion/conversionexception.h>
11#include <cppunit/TestFixture.h>
12#include <cppunit/extensions/HelperMacros.h>
17using namespace CPPUNIT_NS;
35 CPPUNIT_TEST_SUITE_END();
38 void setUp()
override;
66 CPPUNIT_ASSERT(TagValue::empty().isEmpty());
72 const TagValue binary(
"123", 3, TagDataType::Binary);
73 CPPUNIT_ASSERT_EQUAL(TagDataType::Binary, binary.
type());
75 CPPUNIT_ASSERT_THROW(binary.
toString(), ConversionException);
76 CPPUNIT_ASSERT_THROW(binary.
toInteger(), ConversionException);
85 CPPUNIT_ASSERT(!integer.isEmpty());
86 CPPUNIT_ASSERT_EQUAL(TagDataType::Integer, integer.type());
87 CPPUNIT_ASSERT_EQUAL(
static_cast<std::int32_t
>(42), integer.toInteger());
88 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint64_t
>(42), integer.toUnsignedInteger());
89 CPPUNIT_ASSERT_EQUAL(
"42"s, integer.toString());
90 integer.assignInteger(2);
91 CPPUNIT_ASSERT_EQUAL(
"Country"s,
string(Id3Genres::stringFromIndex(integer.toStandardGenreIndex())));
92 integer.assignInteger(Id3Genres::emptyGenreIndex());
93 CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), integer.toStandardGenreIndex());
95 CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), integer.toStandardGenreIndex());
98 integer.assignInteger(-25);
99 CPPUNIT_ASSERT_EQUAL(
"-25"s, integer.toString());
100 CPPUNIT_ASSERT_EQUAL(
PositionInSet(-25), integer.toPositionInSet());
101 CPPUNIT_ASSERT_THROW(integer.toStandardGenreIndex(), ConversionException);
104 integer.assignInteger(0);
105 CPPUNIT_ASSERT_MESSAGE(
"explicitly assigned zero not considered empty", !integer.isEmpty());
106 CPPUNIT_ASSERT_EQUAL(
"0"s, integer.toString());
107 CPPUNIT_ASSERT_EQUAL(
DateTime(), integer.toDateTime());
108 CPPUNIT_ASSERT_EQUAL(
TimeSpan(), integer.toTimeSpan());
112 CPPUNIT_ASSERT_MESSAGE(
"cleared vale considered empty", integer.isEmpty());
113 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"only date (but not type) cleared"s, TagDataType::Integer, integer.type());
114 CPPUNIT_ASSERT_EQUAL(
static_cast<std::int32_t
>(0), integer.toInteger());
115 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint64_t
>(0), integer.toUnsignedInteger());
116 CPPUNIT_ASSERT_EQUAL(
string(), integer.toString());
117 CPPUNIT_ASSERT_EQUAL(
DateTime(), integer.toDateTime());
118 CPPUNIT_ASSERT_EQUAL(
TimeSpan(), integer.toTimeSpan());
123 auto unsignedInteger =
TagValue(
static_cast<std::uint64_t
>(42ul));
124 CPPUNIT_ASSERT(!unsignedInteger.isEmpty());
125 CPPUNIT_ASSERT_EQUAL(TagDataType::UnsignedInteger, unsignedInteger.type());
126 CPPUNIT_ASSERT_EQUAL(
static_cast<std::int32_t
>(42), unsignedInteger.toInteger());
127 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint64_t
>(42), unsignedInteger.toUnsignedInteger());
128 CPPUNIT_ASSERT_EQUAL(
"42"s, unsignedInteger.toString());
129 unsignedInteger.assignUnsignedInteger(2);
130 CPPUNIT_ASSERT_EQUAL(
"Country"s,
string(Id3Genres::stringFromIndex(unsignedInteger.toStandardGenreIndex())));
131 unsignedInteger.assignInteger(Id3Genres::emptyGenreIndex());
132 CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), unsignedInteger.toStandardGenreIndex());
133 unsignedInteger.clearData();
134 CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), unsignedInteger.toStandardGenreIndex());
137 unsignedInteger.assignInteger(0);
138 CPPUNIT_ASSERT_MESSAGE(
"explicitly assigned zero not considered empty", !unsignedInteger.isEmpty());
139 CPPUNIT_ASSERT_EQUAL(
"0"s, unsignedInteger.toString());
140 CPPUNIT_ASSERT_EQUAL(
DateTime(), unsignedInteger.toDateTime());
141 CPPUNIT_ASSERT_EQUAL(
TimeSpan(), unsignedInteger.toTimeSpan());
148 CPPUNIT_ASSERT_EQUAL(4, test.
toInteger());
150 CPPUNIT_ASSERT_EQUAL(
"4/23"s, test.
toString());
152 CPPUNIT_ASSERT_THROW(test.
toDateTime(), ConversionException);
153 CPPUNIT_ASSERT_THROW(test.
toTimeSpan(), ConversionException);
158 const TimeSpan fiveMinutes(TimeSpan::fromMinutes(5));
161 CPPUNIT_ASSERT_EQUAL(timeSpan,
TagValue(timeSpan));
162 CPPUNIT_ASSERT_EQUAL(fiveMinutes, timeSpan.
toTimeSpan());
163 CPPUNIT_ASSERT_EQUAL(fiveMinutes.toString(), timeSpan.
toString());
164 CPPUNIT_ASSERT_THROW(timeSpan.
toInteger(), ConversionException);
165 CPPUNIT_ASSERT_THROW(timeSpan.
toDateTime(), ConversionException);
171 const DateTime now(DateTime::now());
174 CPPUNIT_ASSERT_EQUAL(dateTime,
TagValue(dateTime));
175 CPPUNIT_ASSERT_EQUAL(now, dateTime.
toDateTime());
176 CPPUNIT_ASSERT_EQUAL(now.toString(DateTimeOutputFormat::IsoOmittingDefaultComponents), dateTime.
toString());
177 CPPUNIT_ASSERT_THROW(dateTime.
toInteger(), ConversionException);
178 CPPUNIT_ASSERT_THROW(dateTime.
toTimeSpan(), ConversionException);
184 const auto tagValue =
TagValue(
Popularity{ .
user =
"foo", .rating = 40.0, .playCounter = 123, .scale = TagType::VorbisComment });
186 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to popularity (user)",
"foo"s, popularity.user);
187 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to popularity (rating)", 40.0, popularity.rating);
188 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to popularity (play counter)", std::uint64_t(123), popularity.playCounter);
189 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to popularity (scale)", TagType::VorbisComment, popularity.scale);
190 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to string",
"foo|40|123"s, tagValue.toString());
191 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to string (only rating)",
"43"s,
TagValue(
Popularity{ .
rating = 43 }).toString());
192 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to integer", 40, tagValue.toInteger());
193 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to unsigned integer",
static_cast<std::uint64_t
>(40), tagValue.toUnsignedInteger());
194 CPPUNIT_ASSERT_THROW_MESSAGE(
195 "failing conversion to other type",
TagValue(
"foo|bar"sv, TagTextEncoding::Latin1).toPopularity(), ConversionException);
196 const auto scaledPopularity = tagValue.toScaledPopularity();
197 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"rating scaled to generic scale", 2.0, scaledPopularity.rating);
198 CPPUNIT_ASSERT_THROW_MESSAGE(
199 "failed to scale if no scaling for specified format defined", tagValue.toScaledPopularity(TagType::Mp4Tag), ConversionException);
207 CPPUNIT_ASSERT_EQUAL(
"\x31\0\x35\0"s,
TagValue(15).toString(TagTextEncoding::Utf16LittleEndian));
208 CPPUNIT_ASSERT_EQUAL(
"\0\x31\0\x35"s,
TagValue(15).toString(TagTextEncoding::Utf16BigEndian));
209 CPPUNIT_ASSERT_EQUAL(15,
TagValue(
"\0\x31\0\x35"s, TagTextEncoding::Utf16BigEndian).toInteger());
210 CPPUNIT_ASSERT_EQUAL(
static_cast<std::uint64_t
>(15),
TagValue(
"\0\x31\0\x35"s, TagTextEncoding::Utf16BigEndian).toUnsignedInteger());
211 CPPUNIT_ASSERT_EQUAL_MESSAGE(
213 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"original encoding preserved",
"\0\x31\0\x35"s,
214 TagValue(
"\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian).toString(TagTextEncoding::Unspecified));
215 CPPUNIT_ASSERT_EQUAL_MESSAGE(
217 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"UTF-16 LE BOM truncated",
"\0t\0\xe4\0s\0t"s,
218 TagValue(
"\xff\xfe\0t\0\xe4\0s\0t", 10, TagTextEncoding::Utf16LittleEndian).toString(TagTextEncoding::Unspecified));
219 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"UTF-16 BE BOM truncated",
"t\0\xe4\0s\0t\0"s,
220 TagValue(
"\xfe\xfft\0\xe4\0s\0t\0", 10, TagTextEncoding::Utf16BigEndian).toString(TagTextEncoding::Unspecified));
221 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion via c'tor",
"15\xe4"s,
224 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to int", 15,
TagValue(
"\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian).toInteger());
227 CPPUNIT_ASSERT_EQUAL_MESSAGE(
228 "conversion to pos",
PositionInSet(15),
TagValue(
"\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian).toPositionInSet());
229 CPPUNIT_ASSERT_THROW_MESSAGE(
"failing conversion pos",
TagValue(
"a4 / 15", 7,
TagTextEncoding::Utf8).toPositionInSet(), ConversionException);
230 CPPUNIT_ASSERT_EQUAL_MESSAGE(
232 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to date from UTF-16", DateTime::fromDate(2015, 4, 15),
233 TagValue(
"\0\x32\0\x30\0\x31\0\x35\0\x2d\0\x30\0\x34\0\x2d\0\x31\0\x35", 20, TagTextEncoding::Utf16BigEndian).toDateTime());
235 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to time span", TimeSpan::fromHours(1.5),
TagValue(
"01:30:00", 10,
TagTextEncoding::Utf8).toTimeSpan());
236 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to time span from UTF-16", TimeSpan::fromHours(1.5),
237 TagValue(
"\0\x31\0\x3a\0\x33\0\x30\0\x3a\0\x30\0\x30", 14, TagTextEncoding::Utf16BigEndian).toTimeSpan());
239 CPPUNIT_ASSERT_EQUAL_MESSAGE(
240 "conversion to genre from index", 15,
TagValue(
"\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian).toStandardGenreIndex());
241 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to genre from name", 2,
TagValue(
"Country", 7, TagTextEncoding::Latin1).toStandardGenreIndex());
242 CPPUNIT_ASSERT_THROW_MESSAGE(
243 "failing conversion to genre",
TagValue(
"Kountry", 7, TagTextEncoding::Latin1).toStandardGenreIndex(), ConversionException);
245 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to popularity (user)",
"foo"s, popularity.user);
246 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to popularity (rating)", 42.0, popularity.rating);
247 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"conversion to popularity (play counter)", std::uint64_t(123), popularity.playCounter);
248 CPPUNIT_ASSERT_THROW_MESSAGE(
"failing conversion to popularity",
TagValue(
"foo|bar"sv).toPopularity(), ConversionException);
253 CPPUNIT_ASSERT_MESSAGE(
"equality requires identical types or identical string representation"s,
TagValue(0) != TagValue::empty());
254 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"comparison of equal types"s,
TagValue(15),
TagValue(15));
255 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"types might differ"s,
TagValue(
"15", 2, TagTextEncoding::Latin1),
TagValue(15));
257 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"comparison of equal UTF-16 strings"s,
TagValue(
"\x31\0\x32\0", 4, TagTextEncoding::Utf16LittleEndian),
258 TagValue(
"\x31\0\x32\0", 4, TagTextEncoding::Utf16LittleEndian));
259 CPPUNIT_ASSERT_MESSAGE(
"comparison of different UTF-16 strings"s,
260 TagValue(
"\x31\0\x33\0", 4, TagTextEncoding::Utf16LittleEndian) !=
TagValue(
"\x31\0\x32\0", 4, TagTextEncoding::Utf16LittleEndian));
261 CPPUNIT_ASSERT_EQUAL_MESSAGE(
262 "comparison of equal binary data"s,
TagValue(
"\x31\0\x32\0", 4, TagDataType::Binary),
TagValue(
"\x31\0\x32\0", 4, TagDataType::Binary));
263 CPPUNIT_ASSERT_MESSAGE(
264 "comparison of different binary data"s,
TagValue(
"\x31\0\x33\0", 4, TagDataType::Binary) !=
TagValue(
"\x31\0\x32\0", 4, TagDataType::Binary));
265 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"different encodings are converted if neccassary"s,
TagValue(
"\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian),
266 TagValue(
"15", 2, TagTextEncoding::Latin1));
267 CPPUNIT_ASSERT_EQUAL_MESSAGE(
268 "encoding is ignored when not relevant for types"s,
TagValue(
"\0\x31\0\x35", 4, TagTextEncoding::Utf16BigEndian),
TagValue(15));
269 const TagValue fooTagValue(
"foo", 3, TagDataType::Text), fOoTagValue(
"fOo", 3, TagDataType::Text);
270 CPPUNIT_ASSERT_MESSAGE(
"string comparison case-sensitive by default"s, fooTagValue != fOoTagValue);
271 CPPUNIT_ASSERT_MESSAGE(
"case-insensitive string comparison"s, fooTagValue.compareTo(fOoTagValue, TagValueComparisionFlags::CaseInsensitive));
272 const auto popularity =
Popularity{ .
user =
"some user", .rating = 200, .playCounter = 0 };
274 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"comparison of equal popularity (string and binary representation)"s,
TagValue(
"some user|200.0"sv), first);
275 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"comparison of equal popularity (only binary representation)"s, first, second);
277 CPPUNIT_ASSERT_MESSAGE(
"popularity not equal"s, first !=
TagValue(
Popularity({ .rating = 200 })));
282 CPPUNIT_ASSERT_MESSAGE(
"meta-data must be equal"s, withDescription !=
TagValue(15));
283 CPPUNIT_ASSERT_MESSAGE(
"different meta-data ignored"s, withDescription.
compareTo(
TagValue(15), TagValueComparisionFlags::IgnoreMetaData));
284 TagValue withDescription2(withDescription);
285 CPPUNIT_ASSERT_EQUAL(withDescription, withDescription2);
287 CPPUNIT_ASSERT(withDescription != withDescription2);
289 CPPUNIT_ASSERT_EQUAL(withDescription, withDescription2);
291 CPPUNIT_ASSERT_MESSAGE(
"meta-data case must match by default"s, withDescription != withDescription2);
292 CPPUNIT_ASSERT_MESSAGE(
"meta-data case ignored"s, withDescription.
compareTo(withDescription2, TagValueComparisionFlags::CaseInsensitive));
297 const auto genericZero =
Popularity{ .
rating = 0.0, .scale = TagType::Unspecified };
298 const auto genericMin =
Popularity{ .
rating = 1.0, .scale = TagType::Unspecified };
299 const auto genericMax =
Popularity{ .
rating = 5.0, .scale = TagType::Unspecified };
300 const auto genericMiddle =
Popularity{ .
rating = 3.0, .scale = TagType::Unspecified };
301 const auto id3zero =
Popularity{ .
rating = 0.0, .scale = TagType::Id3v2Tag };
302 const auto id3min =
Popularity{ .
rating = 1.0, .scale = TagType::Id3v2Tag };
303 const auto id3max =
Popularity{ .
rating = 255.0, .scale = TagType::Id3v2Tag };
304 const auto id3middle =
Popularity{ .
rating = 128.0, .scale = TagType::Id3v2Tag };
305 const auto vorbisZero =
Popularity{ .
rating = 0.0, .scale = TagType::VorbisComment };
306 const auto vorbisMin =
Popularity{ .
rating = 20.0, .scale = TagType::VorbisComment };
307 const auto vorbisMax =
Popularity{ .
rating = 100.0, .scale = TagType::OggVorbisComment };
308 const auto vorbisMiddle =
Popularity{ .
rating = 60.0, .scale = TagType::OggVorbisComment };
309 const auto mkvMin =
Popularity{ .
rating = 0.0, .scale = TagType::MatroskaTag };
310 const auto mkvMax =
Popularity{ .
rating = 5.0, .scale = TagType::MatroskaTag };
311 const auto mkvMiddle =
Popularity{ .
rating = 2.5, .scale = TagType::MatroskaTag };
312 for (
const auto &rawZero : { id3zero, vorbisZero }) {
313 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"zero: raw to generic", genericZero.rating, rawZero.scaled(TagType::Unspecified).rating);
314 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"zero: generic to raw ", rawZero.rating, genericZero.scaled(rawZero.scale).rating);
316 for (
const auto &rawMin : { id3min, vorbisMin, mkvMin }) {
317 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"min: raw to generic", genericMin.rating, rawMin.scaled(TagType::Unspecified).rating);
318 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"min: generic to raw ", rawMin.rating, genericMin.scaled(rawMin.scale).rating);
320 for (
const auto &rawMax : { id3max, vorbisMax, mkvMax }) {
321 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"max: raw to generic", genericMax.rating, rawMax.scaled(TagType::Unspecified).rating);
322 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"max: generic to raw ", rawMax.rating, genericMax.scaled(rawMax.scale).rating);
324 for (
const auto &rawMiddle : { id3middle, vorbisMiddle, mkvMiddle }) {
325 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"middle: raw to generic", genericMiddle.rating, rawMiddle.scaled(TagType::Unspecified).rating);
326 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"middle: generic to raw ", rawMiddle.rating, genericMiddle.scaled(rawMiddle.scale).rating);
The PositionInSet class describes the position of an element in a set which consists of a certain num...
The TagValue class wraps values of different types.
void setMimeType(std::string_view mimeType)
Sets the MIME type.
const std::string & mimeType() const
Returns the MIME type.
bool compareTo(const TagValue &other, TagValueComparisionFlags options=TagValueComparisionFlags::None) const
Returns whether both instances are equal.
CppUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
void setDescription(std::string_view value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
std::uint64_t toUnsignedInteger() const
std::int32_t toInteger() const
Converts the value of the current TagValue object to its equivalent integer representation.
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
TagDataType type() const
Returns the type of the assigned value.
void assignTimeSpan(CppUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Popularity toPopularity() const
Converts the value of the current TagValue object to its equivalent Popularity representation.
void assignDateTime(CppUtilities::DateTime value)
Assigns the given DateTime value.
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
The TagValueTests class tests the TagParser::TagValue class.
void testUnsignedInteger()
void testPopularityScaling()
void testEqualityOperator()
Contains all classes and functions of the TagInfo library.
std::string user
The user who gave the rating / played the file, e.g. identified by e-mail address.
double rating
The rating on a tag type specific scale.
CPPUNIT_TEST_SUITE_REGISTRATION(TagValueTests)