#include "../conversion/binaryconversion.h" #include "../conversion/stringbuilder.h" #include "../conversion/stringconversion.h" #include "../tests/testutils.h" using namespace CppUtilities; #include #include #include #include #include #include #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM #include #endif using namespace std; using namespace CPPUNIT_NS; // compile-time checks for binary conversion static_assert(toSynchsafeInt(255) == 383, "toSynchsafeInt()"); static_assert(toNormalInt(383) == 255, "toNormalInt()"); static_assert(swapOrder(static_cast(0xABCD)) == 0xCDAB, "swapOrder(uint16)"); static_assert(swapOrder(0xABCDEF12u) == 0x12EFCDABu, "swapOrder(uint32)"); static_assert(swapOrder(0xABCDEF1234567890ul) == 0x9078563412EFCDABul, "swapOrder(uint64)"); /*! * \brief The ConversionTests class tests classes and functions provided by the files inside the conversion directory. */ class ConversionTests : public TestFixture { CPPUNIT_TEST_SUITE(ConversionTests); CPPUNIT_TEST(testConversionException); CPPUNIT_TEST(testEndianness); CPPUNIT_TEST(testBinaryConversions); CPPUNIT_TEST(testSwapOrderFunctions); CPPUNIT_TEST(testStringEncodingConversions); CPPUNIT_TEST(testStringConversions); CPPUNIT_TEST(testStringBuilder); CPPUNIT_TEST_SUITE_END(); public: ConversionTests(); void setUp() { } void tearDown() { } void testConversionException(); void testEndianness(); void testBinaryConversions(); void testSwapOrderFunctions(); void testStringEncodingConversions(); void testStringConversions(); void testStringBuilder(); private: template void testConversion(const char *message, function vice, function verca, intType min, intType max); char m_buff[8]; random_device m_randomDevice; mt19937 m_randomEngine; }; CPPUNIT_TEST_SUITE_REGISTRATION(ConversionTests); ConversionTests::ConversionTests() : m_randomDevice() , m_randomEngine(m_randomDevice()) { } void ConversionTests::testConversionException() { CPPUNIT_ASSERT(!strcmp("unable to convert", ConversionException().what())); } /*! * \brief Tests whether macros for endianness are correct. */ void ConversionTests::testEndianness() { union { uint32_t integer; char characters[4]; } test = { 0x01020304 }; #if defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN) // test whether macro definitions are consistent CPPUNIT_ASSERT(CONVERSION_UTILITIES_IS_BYTE_ORDER_BIG_ENDIAN == true); CPPUNIT_ASSERT(CONVERSION_UTILITIES_IS_BYTE_ORDER_LITTLE_ENDIAN == false); // test whether byte order assumption is correct CPPUNIT_ASSERT_MESSAGE("Byte order assumption (big-endian) is wrong", test.characters[0] == 0x01); #elif defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN) // test whether macro definitions are consistent CPPUNIT_ASSERT(CONVERSION_UTILITIES_IS_BYTE_ORDER_BIG_ENDIAN == false); CPPUNIT_ASSERT(CONVERSION_UTILITIES_IS_BYTE_ORDER_LITTLE_ENDIAN == true); // test whether byte order assumption is correct CPPUNIT_ASSERT_MESSAGE("Byte order assumption (little-endian) is wrong", test.characters[0] == 0x04); #else CPPUNIT_FAIL("There is not valid byte order assumption"); #endif } template void ConversionTests::testConversion( const char *message, function vice, function versa, intType min, intType max) { const intType random = uniform_int_distribution(min, max)(m_randomEngine); stringstream msg; msg << message << '(' << hex << '0' << 'x' << random << ')'; vice(random, m_buff); CPPUNIT_ASSERT_MESSAGE(msg.str(), versa(m_buff) == random); } #define TEST_TYPE(endianness, function) decltype(endianness::function(m_buff)) #define TEST_CONVERSION(function, endianness) \ testConversion("testing " #function, \ static_cast(&endianness::getBytes), endianness::function, \ numeric_limits::min(), numeric_limits::max()) #define TEST_BE_CONVERSION(function) TEST_CONVERSION(function, BE) #define TEST_LE_CONVERSION(function) TEST_CONVERSION(function, LE) #define TEST_CUSTOM_CONVERSION(vice, versa, endianness, min, max) \ testConversion( \ "testing " #versa, static_cast(&endianness::vice), endianness::versa, min, max) /*! * \brief Tests most important binary conversions. * * Tests toUInt16(), ... toUInt64(), toInt16(), ... toInt64() and * the inverse getBytes() functions with random numbers. */ void ConversionTests::testBinaryConversions() { // test to...() / getBytes() with random numbers for (auto b = 1; b < 100; ++b) { TEST_BE_CONVERSION(toUInt16); TEST_BE_CONVERSION(toUInt32); TEST_BE_CONVERSION(toUInt64); TEST_LE_CONVERSION(toUInt16); TEST_LE_CONVERSION(toUInt32); TEST_LE_CONVERSION(toUInt64); TEST_BE_CONVERSION(toInt16); TEST_BE_CONVERSION(toInt32); TEST_BE_CONVERSION(toInt64); TEST_LE_CONVERSION(toInt16); TEST_LE_CONVERSION(toInt32); TEST_LE_CONVERSION(toInt64); TEST_CUSTOM_CONVERSION(getBytes24, toUInt24, BE, 0, 0xFFFFFF); TEST_CUSTOM_CONVERSION(getBytes24, toUInt24, LE, 0, 0xFFFFFF); } } /*! * \brief Tests swap order functions. */ void ConversionTests::testSwapOrderFunctions() { CPPUNIT_ASSERT(swapOrder(static_cast(0x7825)) == 0x2578); CPPUNIT_ASSERT(swapOrder(static_cast(0x12345678)) == 0x78563412); CPPUNIT_ASSERT(swapOrder(static_cast(0x1122334455667788)) == 0x8877665544332211); } /*! * \brief Internally used for string encoding tests to check results. */ void assertEqual(const char *message, const std::uint8_t *expectedValues, size_t expectedSize, const StringData &actualValues) { // check whether number of elements matches CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expectedSize, actualValues.second); // check whether contents match auto *end = expectedValues + expectedSize; auto *i = reinterpret_cast(actualValues.first.get()); for (; expectedValues != end; ++expectedValues, ++i) { CPPUNIT_ASSERT_EQUAL_MESSAGE(message, asHexNumber(*expectedValues), asHexNumber(*i)); } } #if CONVERSION_UTILITIES_IS_BYTE_ORDER_LITTLE_ENDIAN == true #define LE_STR_FOR_ENDIANNESS(name) name##LE##String #define BE_STR_FOR_ENDIANNESS(name) name##BE##String #elif CONVERSION_UTILITIES_IS_BYTE_ORDER_BIG_ENDIAN == true #define LE_STR_FOR_ENDIANNESS(name) name##BE##String #define BE_STR_FOR_ENDIANNESS(name) name##LE##String #endif /*! * \def LE_STR_FOR_ENDIANNESS * \brief Selects right string for little-endian checks. */ /*! * \def BE_STR_FOR_ENDIANNESS * \brief Selects right string for big-endian checks. */ /*! * \brief Tests string encoding conversions. */ void ConversionTests::testStringEncodingConversions() { // define test string "ABCD" for the different encodings const std::uint8_t simpleString[] = { 'A', 'B', 'C', 'D' }; const std::uint16_t simpleUtf16LEString[] = { 0x0041, 0x0042, 0x0043, 0x0044 }; const std::uint16_t simpleUtf16BEString[] = { 0x4100, 0x4200, 0x4300, 0x4400 }; // define test string "ABĂ–CD" for the different encodings const std::uint8_t latin1String[] = { 'A', 'B', 0xD6, 'C', 'D' }; const std::uint8_t utf8String[] = { 'A', 'B', 0xC3, 0x96, 'C', 'D' }; const std::uint16_t utf16LEString[] = { 0x0041, 0x0042, 0x00D6, 0x0043, 0x0044 }; const std::uint16_t utf16BEString[] = { 0x4100, 0x4200, 0xD600, 0x4300, 0x4400 }; // test conversion to UTF-8 assertEqual("Latin-1 to UTF-8 (simple)", simpleString, 4, convertLatin1ToUtf8(reinterpret_cast(simpleString), 4)); assertEqual("Latin-1 to UTF-8", utf8String, 6, convertLatin1ToUtf8(reinterpret_cast(latin1String), 5)); assertEqual( "UTF-16LE to UTF-8 (simple)", simpleString, 4, convertUtf16LEToUtf8(reinterpret_cast(LE_STR_FOR_ENDIANNESS(simpleUtf16)), 8)); assertEqual("UTF-16LE to UTF-8", utf8String, 6, convertUtf16LEToUtf8(reinterpret_cast(LE_STR_FOR_ENDIANNESS(utf16)), 10)); assertEqual( "UTF-16BE to UTF-8 (simple)", simpleString, 4, convertUtf16BEToUtf8(reinterpret_cast(BE_STR_FOR_ENDIANNESS(simpleUtf16)), 8)); assertEqual("UTF-16BE to UTF-8", utf8String, 6, convertUtf16BEToUtf8(reinterpret_cast(BE_STR_FOR_ENDIANNESS(utf16)), 10)); // test conversion from UTF-8 assertEqual("UTF-8 to Latin-1 (simple)", simpleString, 4, convertUtf8ToLatin1(reinterpret_cast(simpleString), 4)); assertEqual("UTF-8 to Latin-1", latin1String, 5, convertUtf8ToLatin1(reinterpret_cast(utf8String), 6)); assertEqual("UTF-8 to UFT-16LE (simple)", reinterpret_cast(LE_STR_FOR_ENDIANNESS(simpleUtf16)), 8, convertUtf8ToUtf16LE(reinterpret_cast(simpleString), 4)); assertEqual("UTF-8 to UFT-16LE", reinterpret_cast(LE_STR_FOR_ENDIANNESS(utf16)), 10, convertUtf8ToUtf16LE(reinterpret_cast(utf8String), 6)); assertEqual("UTF-8 to UFT-16BE (simple)", reinterpret_cast(BE_STR_FOR_ENDIANNESS(simpleUtf16)), 8, convertUtf8ToUtf16BE(reinterpret_cast(simpleString), 4)); assertEqual("UTF-8 to UFT-16BE", reinterpret_cast(BE_STR_FOR_ENDIANNESS(utf16)), 10, convertUtf8ToUtf16BE(reinterpret_cast(utf8String), 6)); CPPUNIT_ASSERT_THROW(convertString("invalid charset", "UTF-8", "foo", 3, 1.0f), ConversionException); } /*! * \brief Tests miscellaneous string conversions. */ void ConversionTests::testStringConversions() { // stringToNumber() / numberToString() with zero and random numbers CPPUNIT_ASSERT_EQUAL("0"s, numberToString(0)); CPPUNIT_ASSERT_EQUAL("0"s, numberToString(0)); uniform_int_distribution randomDistSigned(numeric_limits::min()); uniform_int_distribution randomDistUnsigned(0); const string stringMsg("string"), wideStringMsg("wide string"), bufferMsg("buffer"); for (std::uint8_t 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 }) { const auto asString = numberToString(unsignedRandom, base); const auto asWideString = numberToString(unsignedRandom, base); CPPUNIT_ASSERT_EQUAL_MESSAGE(stringMsg, unsignedRandom, stringToNumber(asString, base)); CPPUNIT_ASSERT_EQUAL_MESSAGE(wideStringMsg, unsignedRandom, stringToNumber(asWideString, base)); CPPUNIT_ASSERT_EQUAL_MESSAGE(bufferMsg, unsignedRandom, bufferToNumber(asString.data(), asString.size(), base)); } for (const auto base : initializer_list{ 10 }) { const auto asString = numberToString(signedRandom, static_cast(base)); const auto asWideString = numberToString(signedRandom, base); CPPUNIT_ASSERT_EQUAL_MESSAGE(stringMsg, signedRandom, stringToNumber(asString, base)); CPPUNIT_ASSERT_EQUAL_MESSAGE(wideStringMsg, signedRandom, stringToNumber(asWideString, base)); CPPUNIT_ASSERT_EQUAL_MESSAGE(bufferMsg, signedRandom, bufferToNumber(asString.data(), asString.size(), base)); } } // stringToNumber() with spaces at the beginning, leading zeroes, different types and other corner cases CPPUNIT_ASSERT_EQUAL(1, stringToNumber("01")); CPPUNIT_ASSERT_EQUAL(1, stringToNumber(L"01"s)); CPPUNIT_ASSERT_EQUAL(1, stringToNumber(u"01"s)); CPPUNIT_ASSERT_EQUAL(-23, stringToNumber(" - 023"s)); CPPUNIT_ASSERT_EQUAL(-23, bufferToNumber(" - 023", 6)); CPPUNIT_ASSERT_EQUAL(1u, stringToNumber("01")); CPPUNIT_ASSERT_EQUAL(1u, stringToNumber(L"01"s)); CPPUNIT_ASSERT_EQUAL(1u, stringToNumber(u"01"s)); CPPUNIT_ASSERT_EQUAL(23u, stringToNumber(" 023"s)); CPPUNIT_ASSERT_EQUAL(23u, bufferToNumber(" 023", 5)); CPPUNIT_ASSERT_EQUAL(255u, stringToNumber("fF", 16)); CPPUNIT_ASSERT_THROW_MESSAGE("character out of range", stringToNumber("fF", 15), ConversionException); CPPUNIT_ASSERT_THROW_MESSAGE("invalid character", stringToNumber("(", 15), ConversionException); #ifdef __GNUC__ // overflow detection only supported on GCC and Clang CPPUNIT_ASSERT_THROW_MESSAGE("overflow", stringToNumber("100000000", 16), ConversionException); CPPUNIT_ASSERT_THROW_MESSAGE("underflow", stringToNumber("-80000001", 16), ConversionException); CPPUNIT_ASSERT_EQUAL_MESSAGE("positive limit", 0xFFFFFFFFu, stringToNumber("FFFFFFFF", 16)); CPPUNIT_ASSERT_EQUAL_MESSAGE("negative limit", -2147483647, stringToNumber("-2147483647", 10)); #endif // stringToNumber() / numberToString() with floating point numbers CPPUNIT_ASSERT_EQUAL(1.5f, stringToNumber(numberToString(1.5f))); CPPUNIT_ASSERT_EQUAL(1.5, stringToNumber(numberToString(1.5))); CPPUNIT_ASSERT_EQUAL(-10.25, stringToNumber("-10.25")); // interpretIntegerAsString() CPPUNIT_ASSERT_EQUAL("TEST"s, interpretIntegerAsString(0x54455354)); // splitString() / joinStrings() CPPUNIT_ASSERT_EQUAL_MESSAGE("empty string", vector({ string() }), splitString>(string(), ",")); CPPUNIT_ASSERT_EQUAL_MESSAGE( "empty string (simple)", vector({ string_view() }), splitStringSimple>(string_view(), ",")); vector splitTestExpected({ "1", "2,3" }); vector splitTestActual = splitString>("1,2,3"s, ","s, EmptyPartsTreat::Keep, 2); CPPUNIT_ASSERT_EQUAL(splitTestExpected, splitTestActual); splitTestActual = splitStringSimple>("1,2,3"s, ","s, 2); CPPUNIT_ASSERT_EQUAL(splitTestExpected, splitTestActual); splitTestExpected = { "12", "34", "56", "" }; splitTestActual = splitString>("12,34,56,"s, ","s); CPPUNIT_ASSERT_EQUAL(splitTestExpected, splitTestActual); splitTestActual = splitStringSimple>("12,34,56,"s, ","s); CPPUNIT_ASSERT_EQUAL(splitTestExpected, splitTestActual); splitTestExpected = { "1", "2,3", "4,,5" }; splitTestActual = splitString>("1,2,,3,4,,5"s, ","s, EmptyPartsTreat::Merge, 3); CPPUNIT_ASSERT_EQUAL(splitTestExpected, splitTestActual); string splitJoinTest = joinStrings(splitString>(",a,,ab,ABC,s"s, ","s, EmptyPartsTreat::Keep), " "s, false, "("s, ")"s); CPPUNIT_ASSERT_EQUAL("() (a) () (ab) (ABC) (s)"s, splitJoinTest); splitJoinTest = joinStrings(splitString>(",a,,ab,ABC,s"s, ","s, EmptyPartsTreat::Keep), " "s, true, "("s, ")"s); CPPUNIT_ASSERT_EQUAL("(a) (ab) (ABC) (s)"s, splitJoinTest); splitJoinTest = joinStrings(splitStringSimple>(",a,,ab,ABC,s"s, ","s), " "s, true, "("s, ")"s); CPPUNIT_ASSERT_EQUAL("(a) (ab) (ABC) (s)"s, splitJoinTest); splitJoinTest = joinStrings(splitString>(",a,,ab,ABC,s"s, ","s, EmptyPartsTreat::Omit), " "s, false, "("s, ")"s); CPPUNIT_ASSERT_EQUAL("(a) (ab) (ABC) (s)"s, splitJoinTest); splitJoinTest = joinStrings(splitString>(",a,,ab,ABC,s"s, ","s, EmptyPartsTreat::Merge), " "s, false, "("s, ")"s); CPPUNIT_ASSERT_EQUAL("(a,ab) (ABC) (s)"s, splitJoinTest); // findAndReplace() string findReplaceTest("findAndReplace()"); findAndReplace(findReplaceTest, "And", "Or"); CPPUNIT_ASSERT_EQUAL("findOrReplace()"s, findReplaceTest); // startsWith() CPPUNIT_ASSERT(!startsWith(findReplaceTest, "findAnd")); CPPUNIT_ASSERT(startsWith(findReplaceTest, "findOr")); CPPUNIT_ASSERT(!startsWith(findReplaceTest, "findAnd"s)); CPPUNIT_ASSERT(startsWith(findReplaceTest, "findOr"s)); CPPUNIT_ASSERT(startsWith("test"s, "test"s)); CPPUNIT_ASSERT(startsWith("test"s, "test")); CPPUNIT_ASSERT(!startsWith("test"s, "tests"s)); CPPUNIT_ASSERT(!startsWith("test"s, "tests")); // endsWith() CPPUNIT_ASSERT(!endsWith(findReplaceTest, "AndReplace()")); CPPUNIT_ASSERT(endsWith(findReplaceTest, "OrReplace()")); CPPUNIT_ASSERT(!endsWith(findReplaceTest, "AndReplace()"s)); CPPUNIT_ASSERT(endsWith(findReplaceTest, "OrReplace()"s)); CPPUNIT_ASSERT(endsWith("test"s, "test"s)); CPPUNIT_ASSERT(endsWith("test"s, "test")); CPPUNIT_ASSERT(!endsWith("test"s, " test"s)); CPPUNIT_ASSERT(!endsWith("test"s, " test")); // containsSubstrings() CPPUNIT_ASSERT(containsSubstrings("this string contains foo and bar", { "foo", "bar" })); CPPUNIT_ASSERT(!containsSubstrings("this string contains foo and bar", { "bar", "foo" })); // truncateString() string truncateTest("foo bar "); truncateString(truncateTest, ' '); CPPUNIT_ASSERT_EQUAL("foo"s, truncateTest); // encodeBase64() / decodeBase64() with random data uniform_int_distribution randomDistChar; std::uint8_t originalBase64Data[4047]; for (std::uint8_t &c : originalBase64Data) { c = randomDistChar(m_randomEngine); } auto encodedBase64Data = encodeBase64(originalBase64Data, sizeof(originalBase64Data)); auto decodedBase64Data = decodeBase64(encodedBase64Data.data(), static_cast(encodedBase64Data.size())); CPPUNIT_ASSERT(decodedBase64Data.second == sizeof(originalBase64Data)); for (unsigned int i = 0; i < sizeof(originalBase64Data); ++i) { CPPUNIT_ASSERT(decodedBase64Data.first[i] == originalBase64Data[i]); } // test padding encodedBase64Data = encodeBase64(originalBase64Data, sizeof(originalBase64Data) - 1); CPPUNIT_ASSERT_EQUAL('=', encodedBase64Data.at(encodedBase64Data.size() - 1)); CPPUNIT_ASSERT_NO_THROW(decodeBase64(encodedBase64Data.data(), static_cast(encodedBase64Data.size()))); encodedBase64Data = encodeBase64(originalBase64Data, sizeof(originalBase64Data) - 2); CPPUNIT_ASSERT_EQUAL('=', encodedBase64Data.at(encodedBase64Data.size() - 1)); CPPUNIT_ASSERT_EQUAL('=', encodedBase64Data.at(encodedBase64Data.size() - 2)); CPPUNIT_ASSERT_NO_THROW(decodeBase64(encodedBase64Data.data(), static_cast(encodedBase64Data.size()))); // test check for invalid size CPPUNIT_ASSERT_THROW(decodeBase64(encodedBase64Data.data(), 3), ConversionException); // dataSizeToString(), bitrateToString() CPPUNIT_ASSERT_EQUAL("512 bytes"s, dataSizeToString(512ull)); CPPUNIT_ASSERT_EQUAL("2.50 KiB"s, dataSizeToString((2048ull + 512ull))); CPPUNIT_ASSERT_EQUAL("2.50 KiB (2560 byte)"s, dataSizeToString((2048ull + 512ull), true)); CPPUNIT_ASSERT_EQUAL("2.50 MiB"s, dataSizeToString((2048ull + 512ull) * 1024ull)); CPPUNIT_ASSERT_EQUAL("2.50 GiB"s, dataSizeToString((2048ull + 512ull) * 1024ull * 1024ull)); CPPUNIT_ASSERT_EQUAL("2.50 TiB"s, dataSizeToString((2048ull + 512ull) * 1024ull * 1024ull * 1024ull)); CPPUNIT_ASSERT_EQUAL("128 bit/s"s, bitrateToString(0.128, false)); CPPUNIT_ASSERT_EQUAL("128 kbit/s"s, bitrateToString(128.0, false)); CPPUNIT_ASSERT_EQUAL("128 Mbit/s"s, bitrateToString(128.0 * 1e3, false)); CPPUNIT_ASSERT_EQUAL("128 Gbit/s"s, bitrateToString(128.0 * 1e6, false)); CPPUNIT_ASSERT_EQUAL("16 byte/s"s, bitrateToString(0.128, true)); CPPUNIT_ASSERT_EQUAL("16 KiB/s"s, bitrateToString(128.0, true)); CPPUNIT_ASSERT_EQUAL("16 MiB/s"s, bitrateToString(128.0 * 1e3, true)); CPPUNIT_ASSERT_EQUAL("16 GiB/s"s, bitrateToString(128.0 * 1e6, true)); } /// \cond struct ConvertibleToString { operator std::string() const; }; struct StringThatDoesNotLikeToBeCopiedOrMoved : public std::string { explicit StringThatDoesNotLikeToBeCopiedOrMoved(const char *value) : std::string(value) { } [[noreturn]] StringThatDoesNotLikeToBeCopiedOrMoved(const StringThatDoesNotLikeToBeCopiedOrMoved &other) : std::string(other) { CPPUNIT_FAIL("attempt to copy string: " + other); } [[noreturn]] StringThatDoesNotLikeToBeCopiedOrMoved(StringThatDoesNotLikeToBeCopiedOrMoved &&other) : std::string(std::move(other)) { CPPUNIT_FAIL("attempt to move string: " + other); } }; /// \endcond void ConversionTests::testStringBuilder() { // check whether type traits work as expected static_assert(Helper::IsStringType::value); static_assert(!Helper::IsStringType::value); static_assert(Helper::IsStringType::value); static_assert(Helper::IsStringViewType::value); static_assert(!Helper::IsStringViewType::value); static_assert(Helper::IsStringViewType::value); static_assert(Helper::IsConvertibleToConstStringRef::value); #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM static_assert(!Helper::IsConvertibleToConstStringRef::value, "conversion via native() preferred"); #endif static_assert( !Helper::IsConvertibleToConstStringRef::value, "yes, in this context this should not be considered convertible"); static_assert(!Helper::IsConvertibleToConstStringRef::value); #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM static_assert(Helper::IsConvertibleToConstStringRefViaNative::value); #endif static_assert(!Helper::IsConvertibleToConstStringRefViaNative::value); // conversion of string-tuple to string (the actual string builder) const tuple tuple("string1", "string2", 1234, "string3"); CPPUNIT_ASSERT_EQUAL("string1string21234string3"s, tupleToString(tuple)); CPPUNIT_ASSERT_EQUAL("foobarfoo2bar2"s, tupleToString("foo"s % "bar" % "foo2"s % "bar2")); CPPUNIT_ASSERT_EQUAL("v2.3.0"s, argsToString("v2.", 3, '.', 0)); CPPUNIT_ASSERT_EQUAL("v2.3.0"s, argsToString('v', make_tuple(2, '.', 3, '.', 0))); #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM if constexpr (std::is_same_v) { CPPUNIT_ASSERT_EQUAL("path: foo"s, argsToString("path: ", std::filesystem::path("foo"))); } #endif // construction of string-tuple and final conversion to string works CPPUNIT_ASSERT_EQUAL_MESSAGE("result can be passed to any function taking a std::string"s, "123456789"s, "12" % string("34") % '5' % 67 + "89"); constexpr double velocityExample = 27.0; CPPUNIT_ASSERT_EQUAL_MESSAGE("real-word example"s, "velocity: 27 km/h (7.5 m/s)"s, "velocity: " % numberToString(velocityExample) % " km/h (" % numberToString(velocityExample / 3.6) + " m/s)"); CPPUNIT_ASSERT_EQUAL_MESSAGE( "regular + operator still works (no problems with ambiguity)"s, "regular + still works"s, "regular"s + " + still works"); CPPUNIT_ASSERT_EQUAL_MESSAGE("using string_view", "foobar123"s, "foo"sv % "bar"sv + 123); // check that for the internal tuple construction no copies are made StringThatDoesNotLikeToBeCopiedOrMoved str(" happen "); const StringThatDoesNotLikeToBeCopiedOrMoved str2("for this"); CPPUNIT_ASSERT_EQUAL("no copy/move should happen for this!"s, argsToString(StringThatDoesNotLikeToBeCopiedOrMoved("no copy/move should"), str, str2, StringThatDoesNotLikeToBeCopiedOrMoved("!"))); }