From 1b96ab15fd6cef6362f53370d8e1dbe83caa4842 Mon Sep 17 00:00:00 2001 From: Martchus Date: Thu, 17 Aug 2017 18:44:34 +0200 Subject: [PATCH] Test utilities --- CMakeLists.txt | 1 + tests/utils.cpp | 362 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 tests/utils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index de7e9f9..70db321 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,6 +157,7 @@ set(TEST_SRC_FILES tests/overallflac.cpp tests/tagvalue.cpp tests/mediafileinfo.cpp + tests/utils.cpp ) set(DOC_FILES diff --git a/tests/utils.cpp b/tests/utils.cpp new file mode 100644 index 0000000..2ee2b6f --- /dev/null +++ b/tests/utils.cpp @@ -0,0 +1,362 @@ +#include "./helper.h" + +#include "../size.h" +#include "../statusprovider.h" +#include "../tagtarget.h" +#include "../signature.h" +#include "../margin.h" +#include "../aspectratio.h" +#include "../mediaformat.h" +#include "../mediafileinfo.h" +#include "../exceptions.h" +#include "../backuphelper.h" + +#include +#include +using namespace TestUtilities; + +#include +#include + +#include + +using namespace std; +using namespace Media; +using namespace IoUtilities; +using namespace TestUtilities::Literals; + +using namespace CPPUNIT_NS; + +class TestStatusProvider : public StatusProvider +{ +public: + TestStatusProvider(); +}; + +TestStatusProvider::TestStatusProvider() +{ +} + +class UtilitiesTests : public TestFixture { + CPPUNIT_TEST_SUITE(UtilitiesTests); + CPPUNIT_TEST(testSize); + CPPUNIT_TEST(testStatusProvider); + CPPUNIT_TEST(testTagTarget); + CPPUNIT_TEST(testSignature); + CPPUNIT_TEST(testMargin); + CPPUNIT_TEST(testAspectRatio); + CPPUNIT_TEST(testMediaFormat); +#ifdef PLATFORM_UNIX + CPPUNIT_TEST(testBackupFile); +#endif + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void testSize(); + void testStatusProvider(); + void testTagTarget(); + void testSignature(); + void testMargin(); + void testAspectRatio(); + void testMediaFormat(); +#ifdef PLATFORM_UNIX + void testBackupFile(); +#endif +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(UtilitiesTests); + +void UtilitiesTests::setUp() +{ +} + +void UtilitiesTests::tearDown() +{ +} + +void UtilitiesTests::testSize() +{ + static_assert(Size().isNull(), "Size::isNull()"); + static_assert(!Size(3, 4).isNull(), "Size::isNull()"); + static_assert(Size(3, 4).resolution() == 12, "Size::resolution"); + + Size size(1920, 1080); + CPPUNIT_ASSERT_EQUAL("width: 1920, height: 1080"s, size.toString()); + CPPUNIT_ASSERT_EQUAL("1080p"s, string(size.abbreviation())); + size.setWidth(1280); + size.setHeight(720); + CPPUNIT_ASSERT_EQUAL("720p"s, string(size.abbreviation())); +} + +void UtilitiesTests::testStatusProvider() +{ + const string context("unit tests"); + TestStatusProvider status, status2; + + // notifications + CPPUNIT_ASSERT(!status.hasNotifications()); + CPPUNIT_ASSERT_EQUAL(NotificationType::None, status.worstNotificationType()); + status.addNotification(NotificationType::Debug, "debug notification", context); + CPPUNIT_ASSERT_EQUAL(NotificationType::Debug, status.worstNotificationType()); + CPPUNIT_ASSERT(!status.hasCriticalNotifications()); + status.addNotification(NotificationType::Warning, "warning", context); + CPPUNIT_ASSERT_EQUAL(NotificationType::Warning, status.worstNotificationType()); + CPPUNIT_ASSERT_EQUAL("warning"s, status.notifications().back().message()); + CPPUNIT_ASSERT(!status.hasCriticalNotifications()); + status.addNotification(NotificationType::Critical, "error", context); + CPPUNIT_ASSERT_EQUAL(NotificationType::Critical, status.worstNotificationType()); + CPPUNIT_ASSERT(status.hasCriticalNotifications()); + CPPUNIT_ASSERT_EQUAL(3_st, status.notifications().size()); + CPPUNIT_ASSERT(status.hasNotifications()); + status2.addNotifications(status); + status.invalidateNotifications(); + CPPUNIT_ASSERT(!status.hasNotifications()); + CPPUNIT_ASSERT(!status.hasCriticalNotifications()); + CPPUNIT_ASSERT_EQUAL(3_st, status2.notifications().size()); + status.addNotification(status2.notifications().back()); + CPPUNIT_ASSERT(status.hasCriticalNotifications()); + + // status and percentage + CPPUNIT_ASSERT_EQUAL(string(), status.currentStatus()); + CPPUNIT_ASSERT_EQUAL(0.0, status.currentPercentage()); + CPPUNIT_ASSERT(!status.isAborted()); + bool statusUpdateReceived = false, firstStatusUpdate = true; + const auto callbackId = status.registerCallback([&status, &statusUpdateReceived, &firstStatusUpdate] (StatusProvider &sender) { + CPPUNIT_ASSERT(&status == &sender); + if(firstStatusUpdate) { + CPPUNIT_ASSERT_EQUAL("test"s, sender.currentStatus()); + CPPUNIT_ASSERT_EQUAL(0.5, sender.currentPercentage()); + firstStatusUpdate = false; + } + sender.tryToAbort(); + statusUpdateReceived = true; + }); + status.updateStatus("test", 0.5); + CPPUNIT_ASSERT_MESSAGE("status update for updated status received", statusUpdateReceived); + CPPUNIT_ASSERT(status.isAborted()); + statusUpdateReceived = false; + status.updatePercentage(0.625); + CPPUNIT_ASSERT_MESSAGE("status update for updated percentage received", statusUpdateReceived); + statusUpdateReceived = false; + status.addNotification(status2.notifications().front()); + CPPUNIT_ASSERT_MESSAGE("status update for new notification received", statusUpdateReceived); + statusUpdateReceived = false; + status.unregisterCallback(callbackId); + status.updatePercentage(0.65); + CPPUNIT_ASSERT_MESSAGE("no status update received after callback unregistered", !statusUpdateReceived); + + // forwarding + TestStatusProvider forwardReceiver; + status.forwardStatusUpdateCalls(&forwardReceiver); + statusUpdateReceived = false; + forwardReceiver.registerCallback([&status, &statusUpdateReceived] (StatusProvider &sender) { + CPPUNIT_ASSERT(&status == &sender); + CPPUNIT_ASSERT_EQUAL("test2"s, sender.currentStatus()); + CPPUNIT_ASSERT_EQUAL(0.75, sender.currentPercentage()); + statusUpdateReceived = true; + }); + status.updateStatus("test2", 0.75); + CPPUNIT_ASSERT(statusUpdateReceived); +} + +void UtilitiesTests::testTagTarget() +{ + TagTarget target; + CPPUNIT_ASSERT(target.isEmpty()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("default level is 50", 50ul, target.level()); + CPPUNIT_ASSERT_EQUAL("level 50"s, target.toString(TagTargetLevel::Unspecified)); + target = TagTarget(30, {1, 2, 3}, {4}, {5, 6}, {7, 8, 9}); + CPPUNIT_ASSERT(!target.isEmpty()); + const auto mapping = [] (uint64 level) { + return level == 30 ? TagTargetLevel::Track : TagTargetLevel::Unspecified; + }; + CPPUNIT_ASSERT_EQUAL("level 30 'track, song, chapter', track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s, target.toString(mapping)); + target.setLevel(40); + CPPUNIT_ASSERT_EQUAL("level 40, track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s, target.toString(mapping)); + target.setLevelName("test"); + CPPUNIT_ASSERT_EQUAL("level 40 'test', track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s, target.toString(mapping)); + CPPUNIT_ASSERT(target == TagTarget(40, {1, 2, 3}, {4}, {5, 6}, {7, 8, 9})); + target.clear(); + CPPUNIT_ASSERT(target.isEmpty()); + +} + +void UtilitiesTests::testSignature() +{ + const unsigned char xzHead[12] = { + 0xfd, 0x37, 0x7a, 0x58, + 0x5a, 0x00, 0x00, 0x04, + 0xe6, 0xd6, 0xb4, 0x46 + }; + + // truncated buffer + CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast(xzHead), 3)); + CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast(xzHead), 2)); + CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast(xzHead), 0)); + + const auto containerFormat = parseSignature(reinterpret_cast(xzHead), sizeof(xzHead)); + CPPUNIT_ASSERT_EQUAL(ContainerFormat::Xz, containerFormat); + CPPUNIT_ASSERT_EQUAL("xz compressed file"s, string(containerFormatName(containerFormat))); + CPPUNIT_ASSERT_EQUAL("xz"s, string(containerFormatAbbreviation(containerFormat))); + CPPUNIT_ASSERT_EQUAL(string(), string(containerFormatSubversion(containerFormat))); +} + +void UtilitiesTests::testMargin() +{ + static_assert(Margin().isNull(), "empty margin"); + static_assert(!Margin(0, 2).isNull(), "non-empty margin"); + + CPPUNIT_ASSERT_EQUAL("top: 1; left: 2; bottom: 3; right: 4"s, Margin(1, 2, 3, 4).toString()); +} + +void UtilitiesTests::testAspectRatio() +{ + static_assert(!AspectRatio().isValid(), "invalid aspect ratio"); + static_assert(AspectRatio(16, 9).isValid(), "valid aspect ratio"); + static_assert(AspectRatio(16, 9).isExtended(), "extended aspect ratio"); + + const AspectRatio ratio(4); + CPPUNIT_ASSERT_EQUAL(static_cast(16), ratio.numerator); + CPPUNIT_ASSERT_EQUAL(static_cast(11), ratio.denominator); + const AspectRatio ratio2(77); + CPPUNIT_ASSERT_EQUAL(static_cast(0), ratio2.numerator); + CPPUNIT_ASSERT_EQUAL(static_cast(0), ratio2.denominator); +} + +void UtilitiesTests::testMediaFormat() +{ + // unspecific format + MediaFormat aac(GeneralMediaFormat::Aac); + CPPUNIT_ASSERT_EQUAL("Advanced Audio Coding"s, string(aac.name())); + CPPUNIT_ASSERT_EQUAL("AAC"s, string(aac.abbreviation())); + CPPUNIT_ASSERT_EQUAL("AAC"s, string(aac.shortAbbreviation())); + + // specific format + aac += MediaFormat(GeneralMediaFormat::Aac, SubFormats::AacMpeg4LowComplexityProfile, ExtensionFormats::SpectralBandReplication); + CPPUNIT_ASSERT(aac == GeneralMediaFormat::Aac); + CPPUNIT_ASSERT(aac != GeneralMediaFormat::Mpeg1Audio); + CPPUNIT_ASSERT_EQUAL("Advanced Audio Coding Low Complexity Profile"s, string(aac.name())); + CPPUNIT_ASSERT_EQUAL("MPEG-4 AAC-LC"s, string(aac.abbreviation())); + CPPUNIT_ASSERT_EQUAL("HE-AAC"s, string(aac.shortAbbreviation())); + CPPUNIT_ASSERT_EQUAL("Spectral Band Replication / HE-AAC"s, string(aac.extensionName())); +} + +#ifdef PLATFORM_UNIX +void UtilitiesTests::testBackupFile() +{ + using namespace BackupHelper; + + // ensure backup directory is empty, so backups will be created in the same directory + // as the original file + backupDirectory().clear(); + + // setup testfile + MediaFileInfo file(workingCopyPath("unsupported.bin")); + const string workingDir(file.containingDirectory()); + file.open(); + + // create backup file + string backupPath1, backupPath2; + NativeFileStream backupStream1, backupStream2; + createBackupFile(file.path(), backupPath1, file.stream(), backupStream1); + CPPUNIT_ASSERT_EQUAL(workingDir + "/unsupported.bin.bak", backupPath1); + + // recreate original file + file.stream().open(file.path(), ios_base::out); + file.stream() << "test1" << endl; + + // create a 2nd backup which should not override the first one + createBackupFile(file.path(), backupPath2, file.stream(), backupStream2); + CPPUNIT_ASSERT_EQUAL(workingDir + "/unsupported.bin.1.bak", backupPath2); + + // get rid of 2nd backup, recreate original file + backupStream2.close(); + remove(backupPath2.data()); + file.stream().open(file.path(), ios_base::out); + file.stream() << "test2" << endl; + + // create backup under another location + backupDirectory() = workingDir + "/bak"; + try { + createBackupFile(file.path(), backupPath2, file.stream(), backupStream2); + CPPUNIT_FAIL("renaming failed because backup dir does not exist"); + } catch(...) { + const char *what = catchIoFailure(); + CPPUNIT_ASSERT(strstr(what, "Unable to rename original file before rewriting it.")); + } + backupStream2.clear(); + workingCopyPathMode("bak/unsupported.bin", WorkingCopyMode::NoCopy); + createBackupFile(file.path(), backupPath2, file.stream(), backupStream2); + CPPUNIT_ASSERT_EQUAL(workingDir + "/bak/unsupported.bin", backupPath2); + + // get rid of 2nd backup (again) + backupStream2.close(); + remove(backupPath2.data()); + remove(backupDirectory().data()); + + // should be able to use backup stream, eg. seek to the end + backupStream1.seekg(0, ios_base::end); + CPPUNIT_ASSERT_EQUAL(41_st, static_cast(backupStream1.tellg())); + + // restore backup + restoreOriginalFileFromBackupFile(file.path(), backupPath1, file.stream(), backupStream1); + + // check restored backup + file.open(true); + file.stream().seekg(0x1D); + CPPUNIT_ASSERT_EQUAL(static_cast(0x34), file.stream().get()); + file.close(); + + CPPUNIT_ASSERT_MESSAGE("file has no critical notifications yet", !file.hasCriticalNotifications()); + + // reset backup dir again + backupDirectory().clear(); + + // restore after user aborted + createBackupFile(file.path(), backupPath1, file.stream(), backupStream1); + try { + throw OperationAbortedException(); + } catch(...) { + CPPUNIT_ASSERT_THROW(handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, "test"), OperationAbortedException); + } + CPPUNIT_ASSERT(!file.hasCriticalNotifications()); + CPPUNIT_ASSERT(file.hasNotifications()); + CPPUNIT_ASSERT_EQUAL("Rewriting the file to apply changed tag information has been aborted."s, file.notifications().front().message()); + CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, file.notifications().back().message()); + file.invalidateNotifications(); + + // restore after error + createBackupFile(file.path(), backupPath1, file.stream(), backupStream1); + try { + throw Failure(); + } catch(...) { + CPPUNIT_ASSERT_THROW(handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, "test"), Failure); + } + CPPUNIT_ASSERT(file.hasCriticalNotifications()); + CPPUNIT_ASSERT_EQUAL("Rewriting the file to apply changed tag information failed."s, file.notifications().front().message()); + CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, file.notifications().back().message()); + file.invalidateNotifications(); + + // restore after io failure + createBackupFile(file.path(), backupPath1, file.stream(), backupStream1); + try { + throwIoFailure("simulated IO failure"); + } catch(...) { + try { + handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, "test"); + CPPUNIT_FAIL("IO failure rethrown"); + } catch(...) { + catchIoFailure(); + } + } + CPPUNIT_ASSERT(file.hasCriticalNotifications()); + CPPUNIT_ASSERT_EQUAL("An IO error occured when rewriting the file to apply changed tag information."s, file.notifications().front().message()); + CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, file.notifications().back().message()); + file.invalidateNotifications(); + + remove(file.path().data()); +} +#endif