diff --git a/CMakeLists.txt b/CMakeLists.txt index 4035c6a5..223ff25a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -395,6 +395,7 @@ endif() # Subdirectories add_subdirectory(src) add_subdirectory(dist) +add_subdirectory(tests) add_subdirectory(ext/libstrawberry-common) add_subdirectory(ext/libstrawberry-tagreader) add_subdirectory(ext/strawberry-tagreader) diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp index aa31f433..bc1bcb7e 100644 --- a/src/collection/collectionmodel.cpp +++ b/src/collection/collectionmodel.cpp @@ -106,7 +106,8 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q cover_loader_options_.pad_output_image_ = true; cover_loader_options_.scale_output_image_ = true; - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage))); + if (app_) + connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage))); //icon_cache_->setCacheDirectory(Utilities::GetConfigPath(Utilities::Path_CacheRoot) + "/pixmapcache"); //icon_cache_->setMaximumCacheSize(CollectionModel::kIconCacheSize); @@ -172,7 +173,8 @@ void CollectionModel::Init(bool async) { endResetModel(); // Show a loading indicator in the status bar too. - init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs")); + if (app_) + init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs")); ResetAsync(); } @@ -794,7 +796,8 @@ void CollectionModel::ResetAsyncQueryFinished(QFuturetask_manager()->SetTaskFinished(init_task_id_); + if (app_) + app_->task_manager()->SetTaskFinished(init_task_id_); init_task_id_ = -1; } diff --git a/src/organise/organisedialog.cpp b/src/organise/organisedialog.cpp index 6c77d985..5ce4d8e8 100644 --- a/src/organise/organisedialog.cpp +++ b/src/organise/organisedialog.cpp @@ -69,7 +69,8 @@ using std::shared_ptr; using std::stable_sort; -const char *OrganiseDialog::kDefaultFormat = "%albumartist/%album{ (Disc %disc)}/{%track - }%albumartist - %album - %title.%extension"; +const char *OrganiseDialog::kDefaultFormat = "%albumartist/%album{ (Disc %disc)}/{%track - }{%albumartist - }%album{ (Disc %disc)} - %title.%extension"; + const char *OrganiseDialog::kSettingsGroup = "OrganiseDialog"; OrganiseDialog::OrganiseDialog(TaskManager *task_manager, CollectionBackend *backend, QWidget *parent) diff --git a/src/organise/organisedialog.h b/src/organise/organisedialog.h index fecbb5e4..1b9d159e 100644 --- a/src/organise/organisedialog.h +++ b/src/organise/organisedialog.h @@ -59,6 +59,8 @@ class OrganiseDialog : public QDialog { OrganiseDialog(TaskManager *task_manager, CollectionBackend *backend = nullptr, QWidget *parent = nullptr); ~OrganiseDialog(); + static const char *kDefaultFormat; + QSize sizeHint() const; void SetDestinationModel(QAbstractItemModel *model, bool devices = false); @@ -71,6 +73,8 @@ class OrganiseDialog : public QDialog { void SetCopy(bool copy); + static Organise::NewSongInfoList ComputeNewSongsFilenames(const SongList &songs, const OrganiseFormat &format); + signals: void FileCopied(int); @@ -97,10 +101,7 @@ class OrganiseDialog : public QDialog { SongList LoadSongsBlocking(const QStringList &filenames); void SetLoadingSongs(bool loading); - static Organise::NewSongInfoList ComputeNewSongsFilenames(const SongList &songs, const OrganiseFormat &format); - private: - static const char *kDefaultFormat; static const char *kSettingsGroup; Ui_OrganiseDialog *ui_; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..c4f1ad81 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,99 @@ +cmake_minimum_required(VERSION 2.8.11) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -fpermissive -Wno-c++11-narrowing -U__STRICT_ANSI__") + +find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Test) +find_package(GTest) +find_library(GMOCK_LIBRARY gmock) + +if(Qt5Test_FOUND AND GTEST_FOUND AND GMOCK_LIBRARY) + + include_directories(${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS} ${QT_QTTEST_INCLUDE_DIR}) + include_directories(${TAGLIB_INCLUDE_DIRS}) + if(HAVE_GSTREAMER) + link_directories(${GSTREAMER_LIBRARY_DIRS}) + include_directories(${GSTREAMER_INCLUDE_DIRS}) + include_directories(${GSTREAMER_APP_INCLUDE_DIRS}) + include_directories(${GSTREAMER_AUDIO_INCLUDE_DIRS}) + include_directories(${GSTREAMER_BASE_INCLUDE_DIRS}) + include_directories(${GSTREAMER_TAG_INCLUDE_DIRS}) + endif() + include_directories(${CMAKE_SOURCE_DIR}/src) + include_directories(${CMAKE_BINARY_DIR}/src) + include_directories(${CMAKE_SOURCE_DIR}/tests/src) + include_directories(${CMAKE_BINARY_DIR}/tests/src) + include_directories(${CMAKE_SOURCE_DIR}/ext/strawberry-tagreader) + include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-common) + include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader) + include_directories(${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader) + if(HAVE_LIBGPOD) + include_directories(${LIBGPOD_INCLUDE_DIRS}) + endif(HAVE_LIBGPOD) + + add_definitions(-DGTEST_USE_OWN_TR1_TUPLE=1) + + set(TESTUTILS-SOURCES + src/mock_networkaccessmanager.cpp + src/mock_playlistitem.cpp + src/test_utils.cpp + src/testobjectdecorators.cpp + ) + + set(TESTUTILS-MOC-HEADERS src/mock_networkaccessmanager.h src/test_utils.h src/testobjectdecorators.h) + + qt5_wrap_cpp(TESTUTILS-SOURCES-MOC ${TESTUTILS-MOC-HEADERS}) + + add_library(test_utils STATIC EXCLUDE_FROM_ALL ${TESTUTILS-SOURCES} ${TESTUTILS-SOURCES-MOC}) + target_link_libraries(test_utils ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARY} ${QT_LIBRARIES} ${Qt5Test_LIBRARIES}) + + add_custom_target(strawberry_test echo "Running Strawberry tests" WORKING_DIRECTORY ${CURRENT_BINARY_DIR}) + add_custom_target(build_tests WORKING_DIRECTORY ${CURRENT_BINARY_DIR}) + add_dependencies(strawberry_test build_tests) + + qt5_add_resources(TEST-RESOURCE-SOURCES data/testdata.qrc) + + add_library(test_gui_main STATIC EXCLUDE_FROM_ALL ${TEST-RESOURCE-SOURCES} src/main.cpp) + target_link_libraries(test_gui_main strawberry_lib) + set_target_properties(test_gui_main PROPERTIES COMPILE_DEFINITIONS GUI) + + add_library(test_main STATIC EXCLUDE_FROM_ALL ${TEST-RESOURCE-SOURCES} src/main.cpp) + target_link_libraries(test_main strawberry_lib) + + # Given a file foo_test.cpp, creates a target foo_test and adds it to the test target. + macro(add_test_file test_source gui_required) + get_filename_component(TEST_NAME ${test_source} NAME_WE) + add_executable(${TEST_NAME} + EXCLUDE_FROM_ALL + ${test_source} + ) + target_link_libraries(${TEST_NAME} ${GMOCK_LIBRARY} strawberry_lib test_utils Qt5::Test) + set(GUI_REQUIRED ${gui_required}) + if (GUI_REQUIRED) + target_link_libraries(${TEST_NAME} test_gui_main) + else (GUI_REQUIRED) + target_link_libraries(${TEST_NAME} test_main) + endif (GUI_REQUIRED) + + check_cxx_compiler_flag("-Wno-bool-conversions" SUPPORTS_NOBOOL) + if (SUPPORTS_NOBOOL) + set_target_properties(${TEST_NAME} PROPERTIES COMPILE_FLAGS "-Wno-bool-conversions") + endif (SUPPORTS_NOBOOL) + + add_custom_command(TARGET strawberry_test POST_BUILD + COMMAND ./${TEST_NAME}${CMAKE_EXECUTABLE_SUFFIX}) + add_dependencies(build_tests ${TEST_NAME}) + endmacro (add_test_file) + + add_test_file(src/utilities_test.cpp false) + add_test_file(src/concurrentrun_test.cpp false) + add_test_file(src/closure_test.cpp false) + add_test_file(src/mergedproxymodel_test.cpp false) + add_test_file(src/sqlite_test.cpp false) + add_test_file(src/song_test.cpp false) + add_test_file(src/collectionbackend_test.cpp false) + add_test_file(src/collectionmodel_test.cpp true) + add_test_file(src/playlist_test.cpp true) + add_test_file(src/songplaylistitem_test.cpp false) + add_test_file(src/organiseformat_test.cpp false) + +endif() # Qt5Test_FOUND AND GTEST_FOUND AND GMOCK_LIBRARY diff --git a/tests/data/audio/strawberry.aif b/tests/data/audio/strawberry.aif new file mode 100644 index 00000000..9ea29815 Binary files /dev/null and b/tests/data/audio/strawberry.aif differ diff --git a/tests/data/audio/strawberry.asf b/tests/data/audio/strawberry.asf new file mode 100644 index 00000000..588df30a Binary files /dev/null and b/tests/data/audio/strawberry.asf differ diff --git a/tests/data/audio/strawberry.flac b/tests/data/audio/strawberry.flac new file mode 100644 index 00000000..571e1da1 Binary files /dev/null and b/tests/data/audio/strawberry.flac differ diff --git a/tests/data/audio/strawberry.m4a b/tests/data/audio/strawberry.m4a new file mode 100644 index 00000000..9af19c85 Binary files /dev/null and b/tests/data/audio/strawberry.m4a differ diff --git a/tests/data/audio/strawberry.mp3 b/tests/data/audio/strawberry.mp3 new file mode 100644 index 00000000..1eb6c3b6 Binary files /dev/null and b/tests/data/audio/strawberry.mp3 differ diff --git a/tests/data/audio/strawberry.mp4 b/tests/data/audio/strawberry.mp4 new file mode 100644 index 00000000..76cf9514 Binary files /dev/null and b/tests/data/audio/strawberry.mp4 differ diff --git a/tests/data/audio/strawberry.oga b/tests/data/audio/strawberry.oga new file mode 100644 index 00000000..539e1665 Binary files /dev/null and b/tests/data/audio/strawberry.oga differ diff --git a/tests/data/audio/strawberry.opus b/tests/data/audio/strawberry.opus new file mode 100644 index 00000000..62cb11d8 Binary files /dev/null and b/tests/data/audio/strawberry.opus differ diff --git a/tests/data/audio/strawberry.spx b/tests/data/audio/strawberry.spx new file mode 100644 index 00000000..19096cc5 Binary files /dev/null and b/tests/data/audio/strawberry.spx differ diff --git a/tests/data/audio/strawberry.wav b/tests/data/audio/strawberry.wav new file mode 100644 index 00000000..afe82059 Binary files /dev/null and b/tests/data/audio/strawberry.wav differ diff --git a/tests/data/audio/strawberry.wv b/tests/data/audio/strawberry.wv new file mode 100644 index 00000000..872f7913 Binary files /dev/null and b/tests/data/audio/strawberry.wv differ diff --git a/tests/data/testdata.qrc b/tests/data/testdata.qrc new file mode 100644 index 00000000..bce4ae1f --- /dev/null +++ b/tests/data/testdata.qrc @@ -0,0 +1,15 @@ + + + audio/strawberry.wav + audio/strawberry.flac + audio/strawberry.wv + audio/strawberry.oga + audio/strawberry.opus + audio/strawberry.spx + audio/strawberry.aif + audio/strawberry.asf + audio/strawberry.mp3 + audio/strawberry.m4a + audio/strawberry.mp4 + + diff --git a/tests/src/closure_test.cpp b/tests/src/closure_test.cpp new file mode 100644 index 00000000..ce27101b --- /dev/null +++ b/tests/src/closure_test.cpp @@ -0,0 +1,148 @@ +#include "config.h" + +#include +#include + +#include + +#include +#include +#include +#include + +#include "core/closure.h" +#include "test_utils.h" + +TEST(ClosureTest, ClosureInvokesReceiver) { + + TestQObject sender; + TestQObject receiver; + _detail::ClosureBase* closure = NewClosure(&sender, SIGNAL(Emitted()), &receiver, SLOT(Invoke())); + EXPECT_EQ(0, receiver.invoked()); + sender.Emit(); + EXPECT_EQ(1, receiver.invoked()); + +} + +TEST(ClosureTest, ClosureDeletesSelf) { + + TestQObject sender; + TestQObject receiver; + _detail::ClosureBase* closure = NewClosure(&sender, SIGNAL(Emitted()), &receiver, SLOT(Invoke())); + _detail::ObjectHelper* helper = closure->helper(); + QSignalSpy spy(helper, SIGNAL(destroyed())); + EXPECT_EQ(0, receiver.invoked()); + sender.Emit(); + EXPECT_EQ(1, receiver.invoked()); + + EXPECT_EQ(0, spy.count()); + QEventLoop loop; + QObject::connect(helper, SIGNAL(destroyed()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(1, spy.count()); + +} + +TEST(ClosureTest, ClosureDoesNotCrashWithSharedPointerSender) { + + TestQObject receiver; + TestQObject* sender; + std::unique_ptr spy; + QPointer<_detail::ObjectHelper> closure; + { + QSharedPointer sender_shared(new TestQObject); + sender = sender_shared.data(); + closure = QPointer<_detail::ObjectHelper>(NewClosure(sender_shared, SIGNAL(Emitted()), &receiver, SLOT(Invoke()))->helper()); + spy.reset(new QSignalSpy(sender, SIGNAL(destroyed()))); + } + ASSERT_EQ(0, receiver.invoked()); + sender->Emit(); + ASSERT_EQ(1, receiver.invoked()); + + ASSERT_EQ(0, spy->count()); + QEventLoop loop; + QObject::connect(sender, SIGNAL(destroyed()), &loop, SLOT(quit())); + loop.exec(); + ASSERT_EQ(1, spy->count()); + EXPECT_TRUE(closure.isNull()); + +} + +namespace { + +void Foo(bool* called, int question, int* answer) { + + *called = true; + *answer = question; + +} + +} // namespace + +TEST(ClosureTest, ClosureWorksWithFunctionPointers) { + + TestQObject sender; + bool called = false; + int question = 42; + int answer = 0; + NewClosure(&sender, SIGNAL(Emitted()), &Foo, &called, question, &answer); + EXPECT_FALSE(called); + sender.Emit(); + EXPECT_TRUE(called); + EXPECT_EQ(question, answer); + +} + +TEST(ClosureTest, ClosureWorksWithStandardFunctions) { + + TestQObject sender; + bool called = false; + int question = 42; + int answer = 0; + std::function callback(&Foo); + NewClosure(&sender, SIGNAL(Emitted()), callback, &called, question, &answer); + EXPECT_FALSE(called); + sender.Emit(); + EXPECT_TRUE(called); + EXPECT_EQ(question, answer); + +} + +namespace { + +class Bar { + public: + explicit Bar(int a) : foo_(a) {} + bool Foo(int* answer) { + *answer = foo_; + return true; + } + + private: + int foo_; +}; + +} + +TEST(ClosureTest, ClosureWorksWithMemberFunctionPointers) { + + TestQObject sender; + Bar receiver(42); + int q = 1; + NewClosure(&sender, SIGNAL(Emitted()), &receiver, &Bar::Foo, &q); + EXPECT_EQ(1, q); + sender.Emit(); + EXPECT_EQ(42, q); + +} + +TEST(ClosureTest, ClosureCallsLambda) { + + TestQObject sender; + bool called = false; + NewClosure(&sender, SIGNAL(Emitted()), [&called] () { called = true; }); + EXPECT_FALSE(called); + sender.Emit(); + EXPECT_TRUE(called); + +} diff --git a/tests/src/collectionbackend_test.cpp b/tests/src/collectionbackend_test.cpp new file mode 100644 index 00000000..91437b9f --- /dev/null +++ b/tests/src/collectionbackend_test.cpp @@ -0,0 +1,370 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2019, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include + +#include "test_utils.h" + +#include "core/song.h" +#include "core/database.h" +#include "core/logging.h" +#include "collection/collectionbackend.h" +#include "collection/collection.h" + +namespace { + +class CollectionBackendTest : public ::testing::Test { + protected: + virtual void SetUp() { + database_.reset(new MemoryDatabase(nullptr)); + backend_.reset(new CollectionBackend); + backend_->Init(database_.get(), SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable, SCollection::kFtsTable); + } + + Song MakeDummySong(int directory_id) { + // Returns a valid song with all the required fields set + Song ret; + ret.set_directory_id(directory_id); + ret.set_url(QUrl::fromLocalFile("foo.flac")); + ret.set_mtime(1); + ret.set_ctime(1); + ret.set_filesize(1); + return ret; + } + + std::shared_ptr database_; + std::unique_ptr backend_; +}; + +TEST_F(CollectionBackendTest, EmptyDatabase) { + + // Check the database is empty to start with + QStringList artists = backend_->GetAllArtists(); + EXPECT_TRUE(artists.isEmpty()); + + CollectionBackend::AlbumList albums = backend_->GetAllAlbums(); + EXPECT_TRUE(albums.isEmpty()); + +} + +TEST_F(CollectionBackendTest, AddDirectory) { + + QSignalSpy spy(backend_.get(), SIGNAL(DirectoryDiscovered(Directory, SubdirectoryList))); + + backend_->AddDirectory("/tmp"); + + // Check the signal was emitted correctly + ASSERT_EQ(1, spy.count()); + Directory dir = spy[0][0].value(); + EXPECT_EQ(QFileInfo("/tmp").canonicalFilePath(), dir.path); + EXPECT_EQ(1, dir.id); + EXPECT_EQ(0, spy[0][1].value().size()); + +} + +TEST_F(CollectionBackendTest, RemoveDirectory) { + + // Add a directory + Directory dir; + dir.id = 1; + dir.path = "/tmp"; + backend_->AddDirectory(dir.path); + + QSignalSpy spy(backend_.get(), SIGNAL(DirectoryDeleted(Directory))); + + // Remove the directory again + backend_->RemoveDirectory(dir); + + // Check the signal was emitted correctly + ASSERT_EQ(1, spy.count()); + dir = spy[0][0].value(); + EXPECT_EQ("/tmp", dir.path); + EXPECT_EQ(1, dir.id); + +} + +TEST_F(CollectionBackendTest, AddInvalidSong) { + + // Adding a song without certain fields set should fail + backend_->AddDirectory("/tmp"); + Song s; + //s.set_url(QUrl::fromLocalFile("foo.flac")); + s.set_directory_id(1); + + QSignalSpy spy(database_.get(), SIGNAL(Error(QString))); + + backend_->AddOrUpdateSongs(SongList() << s); + //ASSERT_EQ(1, spy.count()); spy.takeFirst(); + + s.set_url(QUrl::fromLocalFile("foo.flac")); + backend_->AddOrUpdateSongs(SongList() << s); + //ASSERT_EQ(1, spy.count()); spy.takeFirst(); + + s.set_filesize(100); + backend_->AddOrUpdateSongs(SongList() << s); + //ASSERT_EQ(1, spy.count()); spy.takeFirst(); + + s.set_mtime(100); + backend_->AddOrUpdateSongs(SongList() << s); + //ASSERT_EQ(1, spy.count()); spy.takeFirst(); + + s.set_ctime(100); + backend_->AddOrUpdateSongs(SongList() << s); + ASSERT_EQ(0, spy.count()); + +} + +TEST_F(CollectionBackendTest, GetAlbumArtNonExistent) { +} + +// Test adding a single song to the database, then getting various information back about it. +class SingleSong : public CollectionBackendTest { + protected: + virtual void SetUp() { + CollectionBackendTest::SetUp(); + + // Add a directory - this will get ID 1 + backend_->AddDirectory("/tmp"); + + // Make a song in that directory + song_ = MakeDummySong(1); + song_.set_title("Title"); + song_.set_artist("Artist"); + song_.set_album("Album"); + song_.set_url(QUrl::fromLocalFile("foo.flac")); + } + + void AddDummySong() { + QSignalSpy added_spy(backend_.get(), SIGNAL(SongsDiscovered(SongList))); + QSignalSpy deleted_spy(backend_.get(), SIGNAL(SongsDeleted(SongList))); + + // Add the song + backend_->AddOrUpdateSongs(SongList() << song_); + + // Check the correct signals were emitted + EXPECT_EQ(0, deleted_spy.count()); + ASSERT_EQ(1, added_spy.count()); + + SongList list = *(reinterpret_cast(added_spy[0][0].data())); + ASSERT_EQ(1, list.count()); + EXPECT_EQ(song_.title(), list[0].title()); + EXPECT_EQ(song_.artist(), list[0].artist()); + EXPECT_EQ(song_.album(), list[0].album()); + EXPECT_EQ(1, list[0].id()); + EXPECT_EQ(1, list[0].directory_id()); + } + + Song song_; + +}; + +TEST_F(SingleSong, GetSongWithNoAlbum) { + + song_.set_album(""); + AddDummySong(); if (HasFatalFailure()) return; + + EXPECT_EQ(1, backend_->GetAllArtists().size()); + CollectionBackend::AlbumList albums = backend_->GetAllAlbums(); + //EXPECT_EQ(1, albums.size()); + //EXPECT_EQ("Artist", albums[0].artist); + //EXPECT_EQ("", albums[0].album_name); + +} + +TEST_F(SingleSong, GetAllArtists) { + + AddDummySong(); if (HasFatalFailure()) return; + + QStringList artists = backend_->GetAllArtists(); + ASSERT_EQ(1, artists.size()); + EXPECT_EQ(song_.artist(), artists[0]); + +} + +TEST_F(SingleSong, GetAllAlbums) { + + AddDummySong(); if (HasFatalFailure()) return; + + CollectionBackend::AlbumList albums = backend_->GetAllAlbums(); + ASSERT_EQ(1, albums.size()); + EXPECT_EQ(song_.album(), albums[0].album_name); + EXPECT_EQ(song_.artist(), albums[0].artist); + +} + +TEST_F(SingleSong, GetAlbumsByArtist) { + + AddDummySong(); if (HasFatalFailure()) return; + + CollectionBackend::AlbumList albums = backend_->GetAlbumsByArtist("Artist"); + ASSERT_EQ(1, albums.size()); + EXPECT_EQ(song_.album(), albums[0].album_name); + EXPECT_EQ(song_.artist(), albums[0].artist); + +} + +TEST_F(SingleSong, GetAlbumArt) { + + AddDummySong(); if (HasFatalFailure()) return; + + CollectionBackend::Album album = backend_->GetAlbumArt("Artist", "AlbumArtist", "Album"); + EXPECT_EQ(song_.album(), album.album_name); + EXPECT_EQ(song_.artist(), album.artist); + +} + +TEST_F(SingleSong, GetSongs) { + + AddDummySong(); if (HasFatalFailure()) return; + + SongList songs = backend_->GetSongs("Artist", "Album"); + ASSERT_EQ(1, songs.size()); + EXPECT_EQ(song_.album(), songs[0].album()); + EXPECT_EQ(song_.artist(), songs[0].artist()); + EXPECT_EQ(song_.title(), songs[0].title()); + EXPECT_EQ(1, songs[0].id()); + +} + +TEST_F(SingleSong, GetSongById) { + + AddDummySong(); if (HasFatalFailure()) return; + + Song song = backend_->GetSongById(1); + EXPECT_EQ(song_.album(), song.album()); + EXPECT_EQ(song_.artist(), song.artist()); + EXPECT_EQ(song_.title(), song.title()); + EXPECT_EQ(1, song.id()); + +} + +TEST_F(SingleSong, FindSongsInDirectory) { + + AddDummySong(); if (HasFatalFailure()) return; + + SongList songs = backend_->FindSongsInDirectory(1); + ASSERT_EQ(1, songs.size()); + EXPECT_EQ(song_.album(), songs[0].album()); + EXPECT_EQ(song_.artist(), songs[0].artist()); + EXPECT_EQ(song_.title(), songs[0].title()); + EXPECT_EQ(1, songs[0].id()); + +} + +TEST_F(SingleSong, UpdateSong) { + + AddDummySong(); if (HasFatalFailure()) return; + + Song new_song(song_); + new_song.set_id(1); + new_song.set_title("A different title"); + + QSignalSpy deleted_spy(backend_.get(), SIGNAL(SongsDeleted(SongList))); + QSignalSpy added_spy(backend_.get(), SIGNAL(SongsDiscovered(SongList))); + + backend_->AddOrUpdateSongs(SongList() << new_song); + + ASSERT_EQ(1, added_spy.size()); + ASSERT_EQ(1, deleted_spy.size()); + + SongList songs_added = *(reinterpret_cast(added_spy[0][0].data())); + SongList songs_deleted = *(reinterpret_cast(deleted_spy[0][0].data())); + ASSERT_EQ(1, songs_added.size()); + ASSERT_EQ(1, songs_deleted.size()); + EXPECT_EQ("Title", songs_deleted[0].title()); + EXPECT_EQ("A different title", songs_added[0].title()); + EXPECT_EQ(1, songs_deleted[0].id()); + EXPECT_EQ(1, songs_added[0].id()); + +} + +TEST_F(SingleSong, DeleteSongs) { + + AddDummySong(); if (HasFatalFailure()) return; + + Song new_song(song_); + new_song.set_id(1); + + QSignalSpy deleted_spy(backend_.get(), SIGNAL(SongsDeleted(SongList))); + + backend_->DeleteSongs(SongList() << new_song); + + ASSERT_EQ(1, deleted_spy.size()); + + SongList songs_deleted = *(reinterpret_cast(deleted_spy[0][0].data())); + ASSERT_EQ(1, songs_deleted.size()); + EXPECT_EQ("Title", songs_deleted[0].title()); + EXPECT_EQ(1, songs_deleted[0].id()); + + // Check we can't retreive that song any more + Song song = backend_->GetSongById(1); + EXPECT_FALSE(song.is_valid()); + EXPECT_EQ(-1, song.id()); + + // And the artist or album shouldn't show up either + QStringList artists = backend_->GetAllArtists(); + EXPECT_EQ(0, artists.size()); + + CollectionBackend::AlbumList albums = backend_->GetAllAlbums(); + EXPECT_EQ(0, albums.size()); + +} + +TEST_F(SingleSong, MarkSongsUnavailable) { + + AddDummySong(); if (HasFatalFailure()) return; + + Song new_song(song_); + new_song.set_id(1); + + QSignalSpy deleted_spy(backend_.get(), SIGNAL(SongsDeleted(SongList))); + + backend_->MarkSongsUnavailable(SongList() << new_song); + + ASSERT_EQ(1, deleted_spy.size()); + + SongList songs_deleted = *(reinterpret_cast(deleted_spy[0][0].data())); + ASSERT_EQ(1, songs_deleted.size()); + EXPECT_EQ("Title", songs_deleted[0].title()); + EXPECT_EQ(1, songs_deleted[0].id()); + + // Check the song is marked as deleted. + Song song = backend_->GetSongById(1); + EXPECT_TRUE(song.is_valid()); + EXPECT_TRUE(song.is_unavailable()); + + // And the artist or album shouldn't show up either + QStringList artists = backend_->GetAllArtists(); + EXPECT_EQ(0, artists.size()); + + CollectionBackend::AlbumList albums = backend_->GetAllAlbums(); + EXPECT_EQ(0, albums.size()); + +} + +} // namespace diff --git a/tests/src/collectionmodel_test.cpp b/tests/src/collectionmodel_test.cpp new file mode 100644 index 00000000..4bc104c7 --- /dev/null +++ b/tests/src/collectionmodel_test.cpp @@ -0,0 +1,335 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2019, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "test_utils.h" + +#include "core/database.h" +#include "collection/collectionmodel.h" +#include "collection/collectionbackend.h" +#include "collection/collection.h" + +namespace { + +class CollectionModelTest : public ::testing::Test { + protected: + void SetUp() { + database_.reset(new MemoryDatabase(nullptr)); + backend_.reset(new CollectionBackend); + backend_->Init(database_.get(), SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable, SCollection::kFtsTable); + model_.reset(new CollectionModel(backend_.get(), nullptr)); + + added_dir_ = false; + + model_sorted_.reset(new QSortFilterProxyModel); + model_sorted_->setSourceModel(model_.get()); + model_sorted_->setSortRole(CollectionModel::Role_SortText); + model_sorted_->setDynamicSortFilter(true); + model_sorted_->sort(0); + } + + Song AddSong(Song& song) { + song.set_directory_id(1); + if (song.mtime() == -1) song.set_mtime(1); + if (song.ctime() == -1) song.set_ctime(1); + if (song.url().isEmpty()) song.set_url(QUrl("file:///tmp/foo")); + if (song.filesize() == -1) song.set_filesize(1); + + if (!added_dir_) { + backend_->AddDirectory("/tmp"); + added_dir_ = true; + } + + backend_->AddOrUpdateSongs(SongList() << song); + return song; + } + + Song AddSong(const QString& title, const QString& artist, const QString& album, int length) { + Song song; + song.Init(title, artist, album, length); + return AddSong(song); + } + + std::shared_ptr database_; + std::unique_ptr backend_; + std::unique_ptr model_; + std::unique_ptr model_sorted_; + + bool added_dir_; +}; + +TEST_F(CollectionModelTest, Initialisation) { + EXPECT_EQ(0, model_->rowCount(QModelIndex())); +} + +TEST_F(CollectionModelTest, WithInitialArtists) { + + AddSong("Title", "Artist 1", "Album", 123); + AddSong("Title", "Artist 2", "Album", 123); + AddSong("Title", "Foo", "Album", 123); + model_->Init(false); + + ASSERT_EQ(5, model_sorted_->rowCount(QModelIndex())); + EXPECT_EQ("A", model_sorted_->index(0, 0, QModelIndex()).data().toString()); + EXPECT_EQ("Artist 1", model_sorted_->index(1, 0, QModelIndex()).data().toString()); + EXPECT_EQ("Artist 2", model_sorted_->index(2, 0, QModelIndex()).data().toString()); + EXPECT_EQ("F", model_sorted_->index(3, 0, QModelIndex()).data().toString()); + EXPECT_EQ("Foo", model_sorted_->index(4, 0, QModelIndex()).data().toString()); + +} + +TEST_F(CollectionModelTest, CompilationAlbums) { + + Song song; + song.Init("Title", "Artist", "Album", 123); + song.set_compilation(true); + + AddSong(song); + model_->Init(false); + model_->fetchMore(model_->index(0, 0)); + + ASSERT_EQ(1, model_->rowCount(QModelIndex())); + + QModelIndex va_index = model_->index(0, 0, QModelIndex()); + EXPECT_EQ("Various artists", va_index.data().toString()); + EXPECT_TRUE(model_->hasChildren(va_index)); + ASSERT_EQ(model_->rowCount(va_index), 1); + + QModelIndex album_index = model_->index(0, 0, va_index); + EXPECT_EQ(model_->data(album_index).toString(), "Album"); + EXPECT_TRUE(model_->hasChildren(album_index)); + +} + +TEST_F(CollectionModelTest, NumericHeaders) { + + AddSong("Title", "1artist", "Album", 123); + AddSong("Title", "2artist", "Album", 123); + AddSong("Title", "0artist", "Album", 123); + AddSong("Title", "zartist", "Album", 123); + model_->Init(false); + + ASSERT_EQ(6, model_sorted_->rowCount(QModelIndex())); + EXPECT_EQ("0-9", model_sorted_->index(0, 0, QModelIndex()).data().toString()); + EXPECT_EQ("0artist", model_sorted_->index(1, 0, QModelIndex()).data().toString()); + EXPECT_EQ("1artist", model_sorted_->index(2, 0, QModelIndex()).data().toString()); + EXPECT_EQ("2artist", model_sorted_->index(3, 0, QModelIndex()).data().toString()); + EXPECT_EQ("Z", model_sorted_->index(4, 0, QModelIndex()).data().toString()); + EXPECT_EQ("zartist", model_sorted_->index(5, 0, QModelIndex()).data().toString()); + +} + +TEST_F(CollectionModelTest, MixedCaseHeaders) { + + AddSong("Title", "Artist", "Album", 123); + AddSong("Title", "artist", "Album", 123); + model_->Init(false); + + ASSERT_EQ(3, model_sorted_->rowCount(QModelIndex())); + EXPECT_EQ("A", model_sorted_->index(0, 0, QModelIndex()).data().toString()); + EXPECT_EQ("Artist", model_sorted_->index(1, 0, QModelIndex()).data().toString()); + EXPECT_EQ("artist", model_sorted_->index(2, 0, QModelIndex()).data().toString()); + +} + +TEST_F(CollectionModelTest, UnknownArtists) { + + AddSong("Title", "", "Album", 123); + model_->Init(false); + model_->fetchMore(model_->index(0, 0)); + + ASSERT_EQ(1, model_->rowCount(QModelIndex())); + QModelIndex unknown_index = model_->index(0, 0, QModelIndex()); + EXPECT_EQ("Unknown", unknown_index.data().toString()); + + ASSERT_EQ(1, model_->rowCount(unknown_index)); + EXPECT_EQ("Album", model_->index(0, 0, unknown_index).data().toString()); + +} + +TEST_F(CollectionModelTest, UnknownAlbums) { + + AddSong("Title", "Artist", "", 123); + AddSong("Title", "Artist", "Album", 123); + model_->Init(false); + model_->fetchMore(model_->index(0, 0)); + + QModelIndex artist_index = model_->index(0, 0, QModelIndex()); + ASSERT_EQ(2, model_->rowCount(artist_index)); + + QModelIndex unknown_album_index = model_->index(0, 0, artist_index); + QModelIndex real_album_index = model_->index(1, 0, artist_index); + + EXPECT_EQ("Unknown", unknown_album_index.data().toString()); + EXPECT_EQ("Album", real_album_index.data().toString()); + +} + +TEST_F(CollectionModelTest, VariousArtistSongs) { + + SongList songs; + for (int i=0 ; i < 4 ; ++i) { + QString n = QString::number(i+1); + Song song; + song.Init("Title " + n, "Artist " + n, "Album", 0); + songs << song; + } + + // Different ways of putting songs in "Various Artist". Make sure they all work + songs[0].set_compilation_detected(true); + songs[1].set_compilation(true); + songs[2].set_compilation_on(true); + songs[3].set_compilation_detected(true); songs[3].set_artist("Various Artists"); + + for (int i=0 ; i<4 ; ++i) + AddSong(songs[i]); + model_->Init(false); + + QModelIndex artist_index = model_->index(0, 0, QModelIndex()); + model_->fetchMore(artist_index); + ASSERT_EQ(1, model_->rowCount(artist_index)); + + QModelIndex album_index = model_->index(0, 0, artist_index); + model_->fetchMore(album_index); + ASSERT_EQ(4, model_->rowCount(album_index)); + + EXPECT_EQ("Artist 1 - Title 1", model_->index(0, 0, album_index).data().toString()); + EXPECT_EQ("Artist 2 - Title 2", model_->index(1, 0, album_index).data().toString()); + EXPECT_EQ("Artist 3 - Title 3", model_->index(2, 0, album_index).data().toString()); + EXPECT_EQ("Title 4", model_->index(3, 0, album_index).data().toString()); + +} + +TEST_F(CollectionModelTest, RemoveSongsLazyLoaded) { + + Song one = AddSong("Title 1", "Artist", "Album", 123); one.set_id(1); + Song two = AddSong("Title 2", "Artist", "Album", 123); two.set_id(2); + AddSong("Title 3", "Artist", "Album", 123); + model_->Init(false); + + // Lazy load the items + QModelIndex artist_index = model_->index(0, 0, QModelIndex()); + model_->fetchMore(artist_index); + ASSERT_EQ(1, model_->rowCount(artist_index)); + QModelIndex album_index = model_->index(0, 0, artist_index); + model_->fetchMore(album_index); + ASSERT_EQ(3, model_->rowCount(album_index)); + + // Remove the first two songs + QSignalSpy spy_preremove(model_.get(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy spy_remove(model_.get(), SIGNAL(rowsRemoved(QModelIndex,int,int))); + QSignalSpy spy_reset(model_.get(), SIGNAL(modelReset())); + + backend_->DeleteSongs(SongList() << one << two); + + ASSERT_EQ(2, spy_preremove.count()); + ASSERT_EQ(2, spy_remove.count()); + ASSERT_EQ(0, spy_reset.count()); + + artist_index = model_->index(0, 0, QModelIndex()); + ASSERT_EQ(1, model_->rowCount(artist_index)); + album_index = model_->index(0, 0, artist_index); + ASSERT_EQ(1, model_->rowCount(album_index)); + EXPECT_EQ("Title 3", model_->index(0, 0, album_index).data().toString()); + +} + +TEST_F(CollectionModelTest, RemoveSongsNotLazyLoaded) { + + Song one = AddSong("Title 1", "Artist", "Album", 123); one.set_id(1); + Song two = AddSong("Title 2", "Artist", "Album", 123); two.set_id(2); + model_->Init(false); + + // Remove the first two songs + QSignalSpy spy_preremove(model_.get(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy spy_remove(model_.get(), SIGNAL(rowsRemoved(QModelIndex,int,int))); + QSignalSpy spy_reset(model_.get(), SIGNAL(modelReset())); + + backend_->DeleteSongs(SongList() << one << two); + + ASSERT_EQ(0, spy_preremove.count()); + ASSERT_EQ(0, spy_remove.count()); + ASSERT_EQ(1, spy_reset.count()); + +} + +TEST_F(CollectionModelTest, RemoveEmptyAlbums) { + + Song one = AddSong("Title 1", "Artist", "Album 1", 123); one.set_id(1); + Song two = AddSong("Title 2", "Artist", "Album 2", 123); two.set_id(2); + Song three = AddSong("Title 3", "Artist", "Album 2", 123); three.set_id(3); + model_->Init(false); + + QModelIndex artist_index = model_->index(0, 0, QModelIndex()); + model_->fetchMore(artist_index); + ASSERT_EQ(2, model_->rowCount(artist_index)); + + // Remove one song from each album + backend_->DeleteSongs(SongList() << one << two); + + // Check the model + artist_index = model_->index(0, 0, QModelIndex()); + model_->fetchMore(artist_index); + ASSERT_EQ(1, model_->rowCount(artist_index)); + QModelIndex album_index = model_->index(0, 0, artist_index); + model_->fetchMore(album_index); + EXPECT_EQ("Album 2", album_index.data().toString()); + + ASSERT_EQ(1, model_->rowCount(album_index)); + EXPECT_EQ("Title 3", model_->index(0, 0, album_index).data().toString()); + +} + +TEST_F(CollectionModelTest, RemoveEmptyArtists) { + + Song one = AddSong("Title", "Artist", "Album", 123); one.set_id(1); + model_->Init(false); + + // Lazy load the items + QModelIndex artist_index = model_->index(0, 0, QModelIndex()); + model_->fetchMore(artist_index); + ASSERT_EQ(1, model_->rowCount(artist_index)); + QModelIndex album_index = model_->index(0, 0, artist_index); + model_->fetchMore(album_index); + ASSERT_EQ(1, model_->rowCount(album_index)); + + // The artist header is there too right? + ASSERT_EQ(2, model_->rowCount(QModelIndex())); + + // Remove the song + backend_->DeleteSongs(SongList() << one); + + // Everything should be gone - even the artist header + ASSERT_EQ(0, model_->rowCount(QModelIndex())); + +} + +} // namespace diff --git a/tests/src/concurrentrun_test.cpp b/tests/src/concurrentrun_test.cpp new file mode 100644 index 00000000..07013f69 --- /dev/null +++ b/tests/src/concurrentrun_test.cpp @@ -0,0 +1,169 @@ +#include + +#include + +#include +#include +#include + +#include "core/concurrentrun.h" +#include "test_utils.h" + +int f() { + return 1337; +} + +TEST(ConcurrentRunTest, ConcurrentRun0StartAndWait) { + + QThreadPool threadpool; + QFuture future = ConcurrentRun::Run(&threadpool, &f); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(1337, watcher.result()); + +} + +int g(int i) { + return ++i; +} + +TEST(ConcurrentRunTest, ConcurrentRun1StartAndWait) { + + QThreadPool threadpool; + int i = 1336; + QFuture future = ConcurrentRun::Run(&threadpool, &g, i); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(1337, watcher.result()); + +} + +int max(int i, int j) { + return (i > j ? i : j); +} + +TEST(ConcurrentRunTest, ConcurrentRun2StartAndWait) { + + int i = 10; + int j = 42; + QThreadPool threadpool; + QFuture future = ConcurrentRun::Run(&threadpool, &max, i, j); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(42, watcher.result()); + +} + +int sum(int a, int b, int c) { + return a + b + c; +} + +TEST(ConcurrentRunTest, ConcurrentRun3StartAndWait) { + + int i = 10; + int j = 42; + int k = 50; + QThreadPool threadpool; + QFuture future = ConcurrentRun::Run(&threadpool, &sum, i, j, k); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(102, watcher.result()); + +} + +void aFunction(int* n) { + *n = 1337; +} + +void bFunction(int* n, int *m) { + aFunction(n); + *m = 1338; +} + +void cFunction(int* n, int *m, int *o) { + bFunction(n, m); + *o = 1339; +} + +TEST(ConcurrentRunTest, ConcurrentRunVoidFunction1Start) { + + QThreadPool threadpool; + + int n = 10; + QFuture future = ConcurrentRun::Run(&threadpool, &aFunction, &n); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(1337, n); + +} + +TEST(ConcurrentRunTest, ConcurrentRunVoidFunction2Start) { + + QThreadPool threadpool; + + int n = 10, m = 11; + QFuture future = ConcurrentRun::Run(&threadpool, &bFunction, &n, &m); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(1337, n); + EXPECT_EQ(1338, m); + +} + +TEST(ConcurrentRunTest, ConcurrentRunVoidFunction3Start) { + + QThreadPool threadpool; + + int n = 10, m = 11, o = 12; + QFuture future = ConcurrentRun::Run(&threadpool, &cFunction, &n, &m, &o); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(1337, n); + EXPECT_EQ(1338, m); + EXPECT_EQ(1339, o); + +} + +class A { + public: + void f(int* i) { + *i = *i + 1; + } +}; + +TEST(ConcurrentRunTest, ConcurrentRunVoidBindFunctionStart) { + + QThreadPool threadpool; + + A a; + int nb = 10; + QFuture future = ConcurrentRun::Run(&threadpool, std::bind(&A::f, &a, &nb)); + QFutureWatcher watcher; + watcher.setFuture(future); + QEventLoop loop; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + EXPECT_EQ(11, nb); + +} diff --git a/tests/src/logging_env.h b/tests/src/logging_env.h new file mode 100644 index 00000000..a663f849 --- /dev/null +++ b/tests/src/logging_env.h @@ -0,0 +1,36 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef LOGGING_ENV_H +#define LOGGING_ENV_H + +#include + +#include "core/logging.h" + +class LoggingEnvironment : public ::testing::Environment { +public: + void SetUp() { + logging::Init(); + logging::SetLevels("*:4"); + } +}; + +#endif // LOGGING_ENV_H diff --git a/tests/src/main.cpp b/tests/src/main.cpp new file mode 100644 index 00000000..1b7ff6c0 --- /dev/null +++ b/tests/src/main.cpp @@ -0,0 +1,44 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include + +#include "logging_env.h" +#include "metatypes_env.h" +#include "resources_env.h" + +int main(int argc, char** argv) { + + testing::InitGoogleMock(&argc, argv); + + testing::AddGlobalTestEnvironment(new MetatypesEnvironment); +#ifdef GUI + QApplication a(argc, argv); +#else + QCoreApplication a(argc, argv); +#endif + testing::AddGlobalTestEnvironment(new ResourcesEnvironment); + testing::AddGlobalTestEnvironment(new LoggingEnvironment); + + return RUN_ALL_TESTS(); + +} diff --git a/tests/src/mergedproxymodel_test.cpp b/tests/src/mergedproxymodel_test.cpp new file mode 100644 index 00000000..fb175ea3 --- /dev/null +++ b/tests/src/mergedproxymodel_test.cpp @@ -0,0 +1,181 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + + +#include "gtest/gtest.h" +#include "test_utils.h" +#include "core/mergedproxymodel.h" + +#include +#include + +class MergedProxyModelTest : public ::testing::Test { + protected: + void SetUp() { + merged_.setSourceModel(&source_); + } + + QStandardItemModel source_; + MergedProxyModel merged_; + +}; + +TEST_F(MergedProxyModelTest, Flat) { + + source_.appendRow(new QStandardItem("one")); + source_.appendRow(new QStandardItem("two")); + + ASSERT_EQ(2, merged_.rowCount(QModelIndex())); + QModelIndex one_i = merged_.index(0, 0, QModelIndex()); + QModelIndex two_i = merged_.index(1, 0, QModelIndex()); + + EXPECT_EQ("one", one_i.data().toString()); + EXPECT_EQ("two", two_i.data().toString()); + EXPECT_FALSE(merged_.parent(one_i).isValid()); + EXPECT_FALSE(merged_.hasChildren(one_i)); + +} + +TEST_F(MergedProxyModelTest, Tree) { + + QStandardItem* one = new QStandardItem("one"); + QStandardItem* two = new QStandardItem("two"); + source_.appendRow(one); + one->appendRow(two); + + ASSERT_EQ(1, merged_.rowCount(QModelIndex())); + QModelIndex one_i = merged_.index(0, 0, QModelIndex()); + + ASSERT_EQ(1, merged_.rowCount(one_i)); + QModelIndex two_i = merged_.index(0, 0, one_i); + + EXPECT_EQ("one", one_i.data().toString()); + EXPECT_EQ("two", two_i.data().toString()); + EXPECT_EQ("one", two_i.parent().data().toString()); + +} + +TEST_F(MergedProxyModelTest, Merged) { + + source_.appendRow(new QStandardItem("one")); + + QStandardItemModel submodel; + submodel.appendRow(new QStandardItem("two")); + + merged_.AddSubModel(source_.index(0, 0, QModelIndex()), &submodel); + + ASSERT_EQ(1, merged_.rowCount(QModelIndex())); + QModelIndex one_i = merged_.index(0, 0, QModelIndex()); + + EXPECT_EQ("one", merged_.data(one_i).toString()); + EXPECT_TRUE(merged_.hasChildren(one_i)); + + ASSERT_EQ(1, merged_.rowCount(one_i)); + QModelIndex two_i = merged_.index(0, 0, one_i); + + EXPECT_EQ("two", merged_.data(two_i).toString()); + EXPECT_EQ(0, merged_.rowCount(two_i)); + EXPECT_FALSE(merged_.hasChildren(two_i)); + +} + +TEST_F(MergedProxyModelTest, SourceInsert) { + + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsInserted(QModelIndex,int,int))); + + source_.appendRow(new QStandardItem("one")); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_FALSE(before_spy[0][0].value().isValid()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_FALSE(after_spy[0][0].value().isValid()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); + +} + +TEST_F(MergedProxyModelTest, SourceRemove) { + + source_.appendRow(new QStandardItem("one")); + + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + source_.removeRow(0, QModelIndex()); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_FALSE(before_spy[0][0].value().isValid()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_FALSE(after_spy[0][0].value().isValid()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); + +} + +TEST_F(MergedProxyModelTest, SubInsert) { + + source_.appendRow(new QStandardItem("one")); + QStandardItemModel submodel; + merged_.AddSubModel(source_.index(0, 0, QModelIndex()), &submodel); + + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsInserted(QModelIndex,int,int))); + + submodel.appendRow(new QStandardItem("two")); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_EQ("one", before_spy[0][0].value().data()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_EQ("one", after_spy[0][0].value().data()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); + +} + +TEST_F(MergedProxyModelTest, SubRemove) { + + source_.appendRow(new QStandardItem("one")); + QStandardItemModel submodel; + merged_.AddSubModel(source_.index(0, 0, QModelIndex()), &submodel); + + submodel.appendRow(new QStandardItem("two")); + + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + submodel.removeRow(0, QModelIndex()); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_EQ("one", before_spy[0][0].value().data()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_EQ("one", after_spy[0][0].value().data()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); + +} diff --git a/tests/src/metatypes_env.h b/tests/src/metatypes_env.h new file mode 100644 index 00000000..0a7d3219 --- /dev/null +++ b/tests/src/metatypes_env.h @@ -0,0 +1,46 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef METATYPES_ENV_H +#define METATYPES_ENV_H + +#include + +#include +#include + +#include "core/song.h" +#include "core/songloader.h" +#include "collection/directory.h" + +class MetatypesEnvironment : public ::testing::Environment { +public: + void SetUp() { + qRegisterMetaType("Directory"); + qRegisterMetaType("DirectoryList"); + qRegisterMetaType("Subdirectory"); + qRegisterMetaType("SubdirectoryList"); + qRegisterMetaType("SongList"); + qRegisterMetaType("QModelIndex"); + qRegisterMetaType("SongLoader::Result"); + } +}; + +#endif // RESOURCES_ENV_H diff --git a/tests/src/mock_collectionbackend.h b/tests/src/mock_collectionbackend.h new file mode 100644 index 00000000..6fe8a350 --- /dev/null +++ b/tests/src/mock_collectionbackend.h @@ -0,0 +1,72 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MOCKCOLLECTIONBACKEND_H +#define MOCKCOLLECTIONBACKEND_H + +#include + +#include "collection/collectionbackend.h" + +class MockCollectionBackend : public CollectionBackendInterface { + public: + MOCK_CONST_METHOD0(songs_table, QString()); + + // Get a list of directories in the collection. Emits DirectoriesDiscovered. + MOCK_METHOD0(LoadDirectoriesAsync, void()); + + // Counts the songs in the collection. + MOCK_METHOD0(UpdateTotalSongCountAsync, void()); + MOCK_METHOD0(UpdateTotalAlbumCountAsync, void()); + MOCK_METHOD0(UpdateTotalArtistCountAsync, void()); + + MOCK_METHOD1(FindSongsInDirectory, SongList(int)); + MOCK_METHOD1(SubdirsInDirectory, SubdirectoryList(int)); + MOCK_METHOD0(GetAllDirectories, DirectoryList()); + MOCK_METHOD3(ChangeDirPath, void(int, const QString&, const QString&)); + + MOCK_METHOD1(GetAllArtists, QStringList(const QueryOptions&)); + MOCK_METHOD1(GetAllArtistsWithAlbums, QStringList(const QueryOptions&)); + MOCK_METHOD3(GetSongs, SongList(const QString&, const QString&, const QueryOptions&)); + + MOCK_METHOD2(GetCompilationSongs, SongList(const QString&, const QueryOptions&)); + + MOCK_METHOD1(GetAllAlbums, AlbumList(const QueryOptions&)); + MOCK_METHOD2(GetAlbumsByArtist, AlbumList(const QString&, const QueryOptions&)); + MOCK_METHOD1(GetCompilationAlbums, AlbumList(const QueryOptions&)); + + MOCK_METHOD4(UpdateManualAlbumArtAsync, void(const QString&, const QString&, const QString&, const QString&)); + MOCK_METHOD3(GetAlbumArt, Album(const QString&, const QString&, const QString&)); + + MOCK_METHOD1(GetSongById, Song(int)); + + MOCK_METHOD1(GetSongsByUrl, SongList(const QUrl&)); + MOCK_METHOD2(GetSongByUrl, Song(const QUrl&, qint64)); + + MOCK_METHOD1(AddDirectory, void(const QString&)); + MOCK_METHOD1(RemoveDirectory, void(const Directory&)); + + MOCK_METHOD1(ExecQuery, bool(CollectionQuery*)); + + MOCK_METHOD2(GetSongsByAlbum, SongList(const QString&, const QueryOptions&)); + +}; + +#endif // MOCKCOLLECTIONBACKEND_H diff --git a/tests/src/mock_networkaccessmanager.cpp b/tests/src/mock_networkaccessmanager.cpp new file mode 100644 index 00000000..e8a7e1a7 --- /dev/null +++ b/tests/src/mock_networkaccessmanager.cpp @@ -0,0 +1,137 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "mock_networkaccessmanager.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using std::min; + +using ::testing::MakeMatcher; +using ::testing::Matcher; +using ::testing::MatcherInterface; +using ::testing::MatchResultListener; +using ::testing::Return; + +class RequestForUrlMatcher : public MatcherInterface { + public: + RequestForUrlMatcher(const QString& contains, const QMap& expected_params) + : contains_(contains), expected_params_(expected_params) {} + + virtual ~RequestForUrlMatcher() {} + + virtual bool Matches(const QNetworkRequest& req) const { + const QUrl& url = req.url(); + + if (!url.toString().contains(contains_)) { + return false; + } + + QUrlQuery url_query(url); + for (QMap::const_iterator it = expected_params_.constBegin(); it != expected_params_.constEnd(); ++it) { + if (!url_query.hasQueryItem(it.key()) || url_query.queryItemValue(it.key()) != it.value()) { + return false; + } + } + return true; + } + + virtual bool MatchAndExplain(const QNetworkRequest& req, MatchResultListener* listener) const { + *listener << "which is " << req.url().toString().toUtf8().constData(); + return Matches(req); + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "matches url"; + } + + private: + QString contains_; + QMap expected_params_; + +}; + +inline Matcher RequestForUrl(const QString& contains, const QMap& params) { + return MakeMatcher(new RequestForUrlMatcher(contains, params)); +} + +MockNetworkReply* MockNetworkAccessManager::ExpectGet(const QString& contains, const QMap& expected_params, int status, const QByteArray& data) { + + MockNetworkReply* reply = new MockNetworkReply(data); + reply->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status); + + EXPECT_CALL(*this, createRequest(GetOperation, RequestForUrl(contains, expected_params), nullptr)). WillOnce(Return(reply)); + + return reply; + +} + +MockNetworkReply::MockNetworkReply() + : data_(nullptr) { +} + +MockNetworkReply::MockNetworkReply(const QByteArray& data) + : data_(data), + pos_(0) { +} + +void MockNetworkReply::SetData(const QByteArray& data) { + data_ = data; + pos_ = 0; +} + +qint64 MockNetworkReply::readData(char* data, qint64 size) { + + if (data_.size() == pos_) { + return -1; + } + qint64 bytes_to_read = min(data_.size() - pos_, size); + memcpy(data, data_.constData() + pos_, bytes_to_read); + pos_ += bytes_to_read; + return bytes_to_read; + +} + +qint64 MockNetworkReply::writeData(const char* data, qint64) { + + ADD_FAILURE() << "Something tried to write to a QNetworkReply"; + return -1; + +} + +void MockNetworkReply::Done() { + + setOpenMode(QIODevice::ReadOnly); + emit finished(); + +} + +void MockNetworkReply::setAttribute(QNetworkRequest::Attribute code, const QVariant& value) { + QNetworkReply::setAttribute(code, value); +} diff --git a/tests/src/mock_networkaccessmanager.h b/tests/src/mock_networkaccessmanager.h new file mode 100644 index 00000000..8f2df95e --- /dev/null +++ b/tests/src/mock_networkaccessmanager.h @@ -0,0 +1,74 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MOCK_NETWORKACCESSMANAGER_H +#define MOCK_NETWORKACCESSMANAGER_H + +#include +#include +#include +#include +#include +#include + +#include "test_utils.h" +#include "gmock/gmock.h" + +// Usage: +// Create a MockNetworkAccessManager. +// Call ExpectGet() with appropriate expectations and the data you want back. +// This will return a MockNetworkReply*. When you are ready for the reply to arrive, call MockNetworkReply::Done(). + +class MockNetworkReply : public QNetworkReply { + Q_OBJECT + public: + MockNetworkReply(); + MockNetworkReply(const QByteArray& data); + + // Use these to set expectations. + void SetData(const QByteArray& data); + virtual void setAttribute(QNetworkRequest::Attribute code, const QVariant& value); + + // Call this when you are ready for the finished() signal. + void Done(); + + protected: + MOCK_METHOD0(abort, void()); + virtual qint64 readData(char* data, qint64); + virtual qint64 writeData(const char* data, qint64); + + QByteArray data_; + qint64 pos_; +}; + + +class MockNetworkAccessManager : public QNetworkAccessManager { + Q_OBJECT + public: + MockNetworkReply* ExpectGet( + const QString& contains, // A string that should be present in the URL. + const QMap& params, // Required URL parameters. + int status, // Returned HTTP status code. + const QByteArray& ret_data); // Returned data. + protected: + MOCK_METHOD3(createRequest, QNetworkReply*(Operation, const QNetworkRequest&, QIODevice*)); +}; + +#endif // MOCK_NETWORKACCESSMANAGER_H diff --git a/tests/src/mock_playlistitem.cpp b/tests/src/mock_playlistitem.cpp new file mode 100644 index 00000000..c7c3b16a --- /dev/null +++ b/tests/src/mock_playlistitem.cpp @@ -0,0 +1,28 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "core/song.h" + +#include "mock_playlistitem.h" + +using ::testing::_; +using ::testing::Return; + +MockPlaylistItem::MockPlaylistItem() : PlaylistItem(Song::Source_LocalFile) {} diff --git a/tests/src/mock_playlistitem.h b/tests/src/mock_playlistitem.h new file mode 100644 index 00000000..5cc1ab57 --- /dev/null +++ b/tests/src/mock_playlistitem.h @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MOCK_PLAYLISTITEM_H +#define MOCK_PLAYLISTITEM_H + +#include + +#include +#include + +#include "core/song.h" +#include "core/settingsprovider.h" +#include "collection/sqlrow.h" +#include "playlist/playlistitem.h" + +class MockPlaylistItem : public PlaylistItem { + + public: + + MockPlaylistItem(); + MOCK_CONST_METHOD0(options, Options()); + MOCK_METHOD1(InitFromQuery, bool(const SqlRow& settings)); + MOCK_METHOD0(Reload, void()); + MOCK_CONST_METHOD0(Metadata, Song()); + MOCK_CONST_METHOD0(Url, QUrl()); + MOCK_METHOD1(SetTemporaryMetadata, void(const Song& metadata)); + MOCK_METHOD0(ClearTemporaryMetadata, void()); + MOCK_METHOD1(DatabaseValue, QVariant(DatabaseColumn)); + +}; + +#endif // MOCK_PLAYLISTITEM_H diff --git a/tests/src/mock_settingsprovider.h b/tests/src/mock_settingsprovider.h new file mode 100644 index 00000000..5ca4c53e --- /dev/null +++ b/tests/src/mock_settingsprovider.h @@ -0,0 +1,54 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef MOCK_SETTINGSPROVIDER_H +#define MOCK_SETTINGSPROVIDER_H + +#include "core/settingsprovider.h" + +#include + +class MockSettingsProvider : public SettingsProvider { + public: + MOCK_METHOD1(set_group, void(const char* group)); + MOCK_CONST_METHOD2(value, QVariant(const QString& key, const QVariant& default_value)); + MOCK_METHOD2(setValue, void(const QString& key, const QVariant& value)); + MOCK_METHOD1(beginReadArray, int(const QString& prefix)); + MOCK_METHOD2(beginWriteArray, void(const QString& prefix, int size)); + MOCK_METHOD1(setArrayIndex, void(int i)); + MOCK_METHOD0(endArray, void()); +}; + +class DummySettingsProvider : public SettingsProvider { + public: + DummySettingsProvider() {} + + void set_group(const char *group) {} + + QVariant value(const QString&, const QVariant& = QVariant()) const { return QVariant(); } + void setValue(const QString&, const QVariant&) {} + int beginReadArray(const QString&) { return 0; } + void beginWriteArray(const QString&, int = -1) {} + void setArrayIndex(int) {} + void endArray() {} + +}; + +#endif // MOCK_SETTINGSPROVIDER_H diff --git a/tests/src/organiseformat_test.cpp b/tests/src/organiseformat_test.cpp new file mode 100644 index 00000000..04f2b9b3 --- /dev/null +++ b/tests/src/organiseformat_test.cpp @@ -0,0 +1,173 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2019, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include +#include "test_utils.h" + +#include "organise/organiseformat.h" +#include "core/timeconstants.h" +#include "core/song.h" +#include "core/logging.h" + +#include + +class OrganiseFormatTest : public ::testing::Test { +protected: + OrganiseFormat format_; + Song song_; +}; + + +TEST_F(OrganiseFormatTest, BasicReplace) { + + song_.set_title("title"); + song_.set_album("album"); + song_.set_artist("artist"); + song_.set_albumartist("albumartist"); + song_.set_track(321); + song_.set_disc(789); + song_.set_year(2010); + song_.set_originalyear(1995); + song_.set_genre("genre"); + song_.set_composer("composer"); + song_.set_performer("performer"); + song_.set_grouping("grouping"); + song_.set_comment("comment"); + song_.set_length_nanosec(987 * kNsecPerSec); + song_.set_samplerate(654); + song_.set_bitdepth(32); + song_.set_bitrate(123); + + format_.set_format("%album %albumartist %artist %bitrate %comment %composer %performer %grouping %disc %genre %length %samplerate %bitdepth %title %track %year"); + + ASSERT_TRUE(format_.IsValid()); + EXPECT_EQ("album_albumartist_artist_123_comment_composer_performer_grouping_789_genre_987_654_32_title_321_2010", format_.GetFilenameForSong(song_)); + +} + +TEST_F(OrganiseFormatTest, Extension) { + + song_.set_url(QUrl("file:///some/path/filename.flac")); + + format_.set_format("%extension"); + ASSERT_TRUE(format_.IsValid()); + EXPECT_EQ("flac", format_.GetFilenameForSong(song_)); + +} + +TEST_F(OrganiseFormatTest, ArtistInitial) { + + song_.set_artist("bob"); + + format_.set_format("%artistinitial"); + ASSERT_TRUE(format_.IsValid()); + EXPECT_EQ("B", format_.GetFilenameForSong(song_)); + +} + +TEST_F(OrganiseFormatTest, AlbumArtistInitial) { + + song_.set_albumartist("bob"); + + format_.set_format("%artistinitial"); + ASSERT_TRUE(format_.IsValid()); + EXPECT_EQ("B", format_.GetFilenameForSong(song_)); + +} + +TEST_F(OrganiseFormatTest, InvalidTag) { + + format_.set_format("%invalid"); + EXPECT_FALSE(format_.IsValid()); + +} + +TEST_F(OrganiseFormatTest, Blocks) { + + format_.set_format("Before{Inside%year}After"); + ASSERT_TRUE(format_.IsValid()); + + song_.set_year(-1); + EXPECT_EQ("BeforeAfter", format_.GetFilenameForSong(song_)); + + song_.set_year(0); + EXPECT_EQ("BeforeAfter", format_.GetFilenameForSong(song_)); + + song_.set_year(123); + EXPECT_EQ("BeforeInside123After", format_.GetFilenameForSong(song_)); + +} + +TEST_F(OrganiseFormatTest, ReplaceSpaces) { + + song_.set_title("The Song Title"); + format_.set_format("The Format String %title"); + + format_.set_replace_spaces(false); + EXPECT_EQ("The Format String The Song Title", format_.GetFilenameForSong(song_)); + format_.set_replace_spaces(true); + EXPECT_EQ("The_Format_String_The_Song_Title", format_.GetFilenameForSong(song_)); + +} + +TEST_F(OrganiseFormatTest, ReplaceNonAscii) { + + song_.set_artist(QString::fromUtf8("Röyksopp")); + format_.set_format("%artist"); + + format_.set_remove_non_ascii(false); + EXPECT_EQ(QString::fromUtf8("Röyksopp"), format_.GetFilenameForSong(song_)); + format_.set_remove_non_ascii(true); + EXPECT_EQ("Royksopp", format_.GetFilenameForSong(song_)); + + song_.set_artist(QString::fromUtf8("Владимир Высоцкий")); + EXPECT_EQ("_________________", format_.GetFilenameForSong(song_)); + +} + +TEST_F(OrganiseFormatTest, TrackNumberPadding) { + + format_.set_format("%track"); + + song_.set_track(9); + EXPECT_EQ("09", format_.GetFilenameForSong(song_)); + + song_.set_track(99); + EXPECT_EQ("99", format_.GetFilenameForSong(song_)); + + song_.set_track(999); + EXPECT_EQ("999", format_.GetFilenameForSong(song_)); + + song_.set_track(0); + EXPECT_EQ("", format_.GetFilenameForSong(song_)); + +} + +#if 0 +TEST_F(OrganiseFormatTest, ReplaceSlashes) { + + format_.set_format("%title"); + song_.set_title("foo/bar\\baz"); + qLog(Debug) << format_.GetFilenameForSong(song_); + EXPECT_EQ("foo_bar_baz", format_.GetFilenameForSong(song_)); + +} +#endif diff --git a/tests/src/playlist_test.cpp b/tests/src/playlist_test.cpp new file mode 100644 index 00000000..df3334f4 --- /dev/null +++ b/tests/src/playlist_test.cpp @@ -0,0 +1,543 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2019, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include + +#include "test_utils.h" + +#include "collection/collectionplaylistitem.h" +#include "playlist/playlist.h" +#include "mock_settingsprovider.h" +#include "mock_playlistitem.h" + +#include +#include + +using std::shared_ptr; +using ::testing::Return; + +namespace { + +class PlaylistTest : public ::testing::Test { + protected: + PlaylistTest() + : playlist_(nullptr, nullptr, nullptr, 1), + sequence_(nullptr, new DummySettingsProvider) + { + } + + void SetUp() { + playlist_.set_sequence(&sequence_); + } + + MockPlaylistItem* MakeMockItem(const QString& title, const QString& artist = QString(), const QString& album = QString(), int length = 123) const { + Song metadata; + metadata.Init(title, artist, album, length); + + MockPlaylistItem* ret = new MockPlaylistItem; + EXPECT_CALL(*ret, Metadata()).WillRepeatedly(Return(metadata)); + + return ret; + } + + shared_ptr MakeMockItemP(const QString& title, const QString& artist = QString(), const QString& album = QString(), int length = 123) const { + return shared_ptr(MakeMockItem(title, artist, album, length)); + } + + Playlist playlist_; + PlaylistSequence sequence_; + +}; + +TEST_F(PlaylistTest, Basic) { + EXPECT_EQ(0, playlist_.rowCount(QModelIndex())); +} + +TEST_F(PlaylistTest, InsertItems) { + + MockPlaylistItem* item = MakeMockItem("Title", "Artist", "Album", 123); + shared_ptr item_ptr(item); + + // Insert the item + EXPECT_EQ(0, playlist_.rowCount(QModelIndex())); + playlist_.InsertItems(PlaylistItemList() << item_ptr, -1); + ASSERT_EQ(1, playlist_.rowCount(QModelIndex())); + + // Get the metadata + EXPECT_EQ("Title", playlist_.data(playlist_.index(0, Playlist::Column_Title))); + EXPECT_EQ("Artist", playlist_.data(playlist_.index(0, Playlist::Column_Artist))); + EXPECT_EQ("Album", playlist_.data(playlist_.index(0, Playlist::Column_Album))); + EXPECT_EQ(123, playlist_.data(playlist_.index(0, Playlist::Column_Length))); + +} + +TEST_F(PlaylistTest, Indexes) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + // Start "playing" track 1 + playlist_.set_current_row(0); + EXPECT_EQ(0, playlist_.current_row()); + EXPECT_EQ("One", playlist_.current_item()->Metadata().title()); + EXPECT_EQ(-1, playlist_.previous_row()); + EXPECT_EQ(1, playlist_.next_row()); + + // Stop playing + EXPECT_EQ(0, playlist_.last_played_row()); + playlist_.set_current_row(-1); + EXPECT_EQ(0, playlist_.last_played_row()); + EXPECT_EQ(-1, playlist_.current_row()); + + // Play track 2 + playlist_.set_current_row(1); + EXPECT_EQ(1, playlist_.current_row()); + EXPECT_EQ("Two", playlist_.current_item()->Metadata().title()); + EXPECT_EQ(0, playlist_.previous_row()); + EXPECT_EQ(2, playlist_.next_row()); + + // Play track 3 + playlist_.set_current_row(2); + EXPECT_EQ(2, playlist_.current_row()); + EXPECT_EQ("Three", playlist_.current_item()->Metadata().title()); + EXPECT_EQ(1, playlist_.previous_row()); + EXPECT_EQ(-1, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, RepeatPlaylist) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + playlist_.sequence()->SetRepeatMode(PlaylistSequence::Repeat_Playlist); + + playlist_.set_current_row(0); + EXPECT_EQ(1, playlist_.next_row()); + + playlist_.set_current_row(1); + EXPECT_EQ(2, playlist_.next_row()); + + playlist_.set_current_row(2); + EXPECT_EQ(0, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, RepeatTrack) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + playlist_.sequence()->SetRepeatMode(PlaylistSequence::Repeat_Track); + + playlist_.set_current_row(0); + EXPECT_EQ(0, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, RepeatAlbum) { + + playlist_.InsertItems(PlaylistItemList() + << MakeMockItemP("One", "Album one") + << MakeMockItemP("Two", "Album two") + << MakeMockItemP("Three", "Album one")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + playlist_.sequence()->SetRepeatMode(PlaylistSequence::Repeat_Album); + + playlist_.set_current_row(0); + EXPECT_EQ(2, playlist_.next_row()); + + playlist_.set_current_row(2); + EXPECT_EQ(0, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, RemoveBeforeCurrent) { + + playlist_.InsertItems(PlaylistItemList() + << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + // Remove a row before the currently playing track + playlist_.set_current_row(2); + EXPECT_EQ(2, playlist_.current_row()); + playlist_.removeRow(1, QModelIndex()); + EXPECT_EQ(1, playlist_.current_row()); + EXPECT_EQ(1, playlist_.last_played_row()); + EXPECT_EQ(0, playlist_.previous_row()); + EXPECT_EQ(-1, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, RemoveAfterCurrent) { + + playlist_.InsertItems(PlaylistItemList() + << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + // Remove a row after the currently playing track + playlist_.set_current_row(0); + EXPECT_EQ(0, playlist_.current_row()); + playlist_.removeRow(1, QModelIndex()); + EXPECT_EQ(0, playlist_.current_row()); + EXPECT_EQ(0, playlist_.last_played_row()); + EXPECT_EQ(-1, playlist_.previous_row()); + EXPECT_EQ(1, playlist_.next_row()); + + playlist_.set_current_row(1); + EXPECT_EQ(-1, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, RemoveCurrent) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + // Remove the currently playing track's row + playlist_.set_current_row(1); + EXPECT_EQ(1, playlist_.current_row()); + playlist_.removeRow(1, QModelIndex()); + EXPECT_EQ(-1, playlist_.current_row()); + EXPECT_EQ(-1, playlist_.last_played_row()); + EXPECT_EQ(-1, playlist_.previous_row()); + EXPECT_EQ(0, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, InsertBeforeCurrent) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + playlist_.set_current_row(1); + EXPECT_EQ(1, playlist_.current_row()); + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("Four"), 0); + ASSERT_EQ(4, playlist_.rowCount(QModelIndex())); + + EXPECT_EQ(2, playlist_.current_row()); + EXPECT_EQ(2, playlist_.last_played_row()); + EXPECT_EQ(1, playlist_.previous_row()); + EXPECT_EQ(3, playlist_.next_row()); + + EXPECT_EQ("Four", playlist_.data(playlist_.index(0, Playlist::Column_Title))); + EXPECT_EQ("One", playlist_.data(playlist_.index(1, Playlist::Column_Title))); + +} + +TEST_F(PlaylistTest, InsertAfterCurrent) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + playlist_.set_current_row(1); + EXPECT_EQ(1, playlist_.current_row()); + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("Four"), 2); + ASSERT_EQ(4, playlist_.rowCount(QModelIndex())); + + EXPECT_EQ(1, playlist_.current_row()); + EXPECT_EQ(1, playlist_.last_played_row()); + EXPECT_EQ(0, playlist_.previous_row()); + EXPECT_EQ(2, playlist_.next_row()); + + EXPECT_EQ("Two", playlist_.data(playlist_.index(1, Playlist::Column_Title))); + EXPECT_EQ("Four", playlist_.data(playlist_.index(2, Playlist::Column_Title))); + EXPECT_EQ("Three", playlist_.data(playlist_.index(3, Playlist::Column_Title))); + +} + +TEST_F(PlaylistTest, Clear) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + playlist_.set_current_row(1); + EXPECT_EQ(1, playlist_.current_row()); + playlist_.Clear(); + + EXPECT_EQ(0, playlist_.rowCount(QModelIndex())); + EXPECT_EQ(-1, playlist_.current_row()); + EXPECT_EQ(-1, playlist_.last_played_row()); + EXPECT_EQ(-1, playlist_.previous_row()); + EXPECT_EQ(-1, playlist_.next_row()); + +} + +TEST_F(PlaylistTest, UndoAdd) { + + EXPECT_FALSE(playlist_.undo_stack()->canUndo()); + EXPECT_FALSE(playlist_.undo_stack()->canRedo()); + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("Title")); + EXPECT_EQ(1, playlist_.rowCount(QModelIndex())); + EXPECT_FALSE(playlist_.undo_stack()->canRedo()); + ASSERT_TRUE(playlist_.undo_stack()->canUndo()); + + playlist_.undo_stack()->undo(); + EXPECT_EQ(0, playlist_.rowCount(QModelIndex())); + EXPECT_FALSE(playlist_.undo_stack()->canUndo()); + ASSERT_TRUE(playlist_.undo_stack()->canRedo()); + + playlist_.undo_stack()->redo(); + EXPECT_EQ(1, playlist_.rowCount(QModelIndex())); + EXPECT_FALSE(playlist_.undo_stack()->canRedo()); + EXPECT_TRUE(playlist_.undo_stack()->canUndo()); + + EXPECT_EQ("Title", playlist_.data(playlist_.index(0, Playlist::Column_Title))); + +} + +TEST_F(PlaylistTest, UndoMultiAdd) { + + // Add 1 item + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One")); + + // Add 2 items + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("Two") << MakeMockItemP("Three")); + + // Undo adding 2 items + ASSERT_TRUE(playlist_.undo_stack()->canUndo()); + EXPECT_EQ("add 2 songs", playlist_.undo_stack()->undoText()); + playlist_.undo_stack()->undo(); + + // Undo adding 1 item + ASSERT_TRUE(playlist_.undo_stack()->canUndo()); + EXPECT_EQ("add 1 songs", playlist_.undo_stack()->undoText()); + playlist_.undo_stack()->undo(); + + EXPECT_FALSE(playlist_.undo_stack()->canUndo()); + +} + +#if 0 +TEST_F(PlaylistTest, UndoRemove) { + + EXPECT_FALSE(playlist_.undo_stack()->canUndo()); + EXPECT_FALSE(playlist_.undo_stack()->canRedo()); + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("Title")); + playlist_.removeRow(0); + + EXPECT_EQ(0, playlist_.rowCount(QModelIndex())); + EXPECT_FALSE(playlist_.undo_stack()->canRedo()); + ASSERT_TRUE(playlist_.undo_stack()->canUndo()); + + playlist_.undo_stack()->undo(); + //EXPECT_EQ(1, playlist_.rowCount(QModelIndex())); + //ASSERT_TRUE(playlist_.undo_stack()->canRedo()); + + //EXPECT_EQ("Title", playlist_.data(playlist_.index(0, Playlist::Column_Title))); + + //playlist_.undo_stack()->redo(); + //EXPECT_EQ(0, playlist_.rowCount(QModelIndex())); + //EXPECT_FALSE(playlist_.undo_stack()->canRedo()); + //EXPECT_TRUE(playlist_.undo_stack()->canUndo()); + +} + +TEST_F(PlaylistTest, UndoMultiRemove) { + + // Add 3 items + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + // Remove 1 item + playlist_.removeRow(1); // Item "Two" + + // Remove 2 items + playlist_.removeRows(0, 2); // "One" and "Three" + + ASSERT_EQ(0, playlist_.rowCount(QModelIndex())); + + // Undo removing all 3 items + ASSERT_TRUE(playlist_.undo_stack()->canUndo()); + EXPECT_EQ("remove 3 songs", playlist_.undo_stack()->undoText()); + playlist_.undo_stack()->undo(); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); +} +#endif + +TEST_F(PlaylistTest, UndoClear) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("One") << MakeMockItemP("Two") << MakeMockItemP("Three")); + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + + playlist_.Clear(); + ASSERT_EQ(0, playlist_.rowCount(QModelIndex())); + ASSERT_TRUE(playlist_.undo_stack()->canUndo()); + EXPECT_EQ("remove 3 songs", playlist_.undo_stack()->undoText()); + playlist_.undo_stack()->undo(); + + ASSERT_EQ(3, playlist_.rowCount(QModelIndex())); + +} + +#if 0 +TEST_F(PlaylistTest, UndoRemoveCurrent) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("Title")); + playlist_.set_current_row(0); + EXPECT_EQ(0, playlist_.current_row()); + EXPECT_EQ(0, playlist_.last_played_row()); + + playlist_.removeRow(0); + EXPECT_EQ(-1, playlist_.current_row()); + EXPECT_EQ(-1, playlist_.last_played_row()); + + playlist_.undo_stack()->undo(); + EXPECT_EQ(0, playlist_.current_row()); + EXPECT_EQ(0, playlist_.last_played_row()); + +} + +TEST_F(PlaylistTest, UndoRemoveOldCurrent) { + + playlist_.InsertItems(PlaylistItemList() << MakeMockItemP("Title")); + playlist_.set_current_row(0); + EXPECT_EQ(0, playlist_.current_row()); + EXPECT_EQ(0, playlist_.last_played_row()); + + playlist_.removeRow(0); + EXPECT_EQ(-1, playlist_.current_row()); + EXPECT_EQ(-1, playlist_.last_played_row()); + + playlist_.set_current_row(-1); + + playlist_.undo_stack()->undo(); + EXPECT_EQ(0, playlist_.current_row()); + EXPECT_EQ(0, playlist_.last_played_row()); + +} + +TEST_F(PlaylistTest, ShuffleThenNext) { + + // Add 100 items + PlaylistItemList items; + for (int i=0 ; i<100 ; ++i) + items << MakeMockItemP("Item " + QString::number(i)); + playlist_.InsertItems(items); + + playlist_.set_current_row(0); + + // Shuffle until the current index is not at the end + forever { + playlist_.Shuffle(); + if (playlist_.current_row() != items.count()-1) + break; + } + + int index = playlist_.current_row(); + EXPECT_EQ("Item 0", playlist_.current_item()->Metadata().title()); + EXPECT_EQ("Item 0", playlist_.data(playlist_.index(index, Playlist::Column_Title))); + EXPECT_EQ(index, playlist_.last_played_row()); + EXPECT_EQ(index + 1, playlist_.next_row()); + + // Shuffle until the current index *is* at the end + forever { + playlist_.Shuffle(); + if (playlist_.current_row() == items.count()-1) + break; + } + + index = playlist_.current_row(); + EXPECT_EQ("Item 0", playlist_.current_item()->Metadata().title()); + EXPECT_EQ("Item 0", playlist_.data(playlist_.index(index, Playlist::Column_Title))); + EXPECT_EQ(index, playlist_.last_played_row()); + EXPECT_EQ(-1, playlist_.next_row()); + EXPECT_EQ(index-1, playlist_.previous_row()); + +} +#endif + +TEST_F(PlaylistTest, CollectionIdMapSingle) { + + Song song; + song.Init("title", "artist", "album", 123); + song.set_id(1); + + PlaylistItemPtr item(new CollectionPlaylistItem(song)); + playlist_.InsertItems(PlaylistItemList() << item); + + EXPECT_EQ(0, playlist_.collection_items_by_id(-1).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(0).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(2).count()); + ASSERT_EQ(1, playlist_.collection_items_by_id(1).count()); + EXPECT_EQ(song.title(), playlist_.collection_items_by_id(1)[0]->Metadata().title()); + + playlist_.Clear(); + + EXPECT_EQ(0, playlist_.collection_items_by_id(1).count()); + +} + +TEST_F(PlaylistTest, CollectionIdMapInvalid) { + + Song invalid; + invalid.Init("title", "artist", "album", 123); + ASSERT_EQ(-1, invalid.id()); + + PlaylistItemPtr item(new CollectionPlaylistItem(invalid)); + playlist_.InsertItems(PlaylistItemList() << item); + + EXPECT_EQ(0, playlist_.collection_items_by_id(-1).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(0).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(1).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(2).count()); + +} + +TEST_F(PlaylistTest, CollectionIdMapMulti) { + + Song one; + one.Init("title", "artist", "album", 123); + one.set_id(1); + + Song two; + two.Init("title 2", "artist 2", "album 2", 123); + two.set_id(2); + + PlaylistItemPtr item_one(new CollectionPlaylistItem(one)); + PlaylistItemPtr item_two(new CollectionPlaylistItem(two)); + PlaylistItemPtr item_three(new CollectionPlaylistItem(one)); + playlist_.InsertItems(PlaylistItemList() << item_one << item_two << item_three); + + EXPECT_EQ(2, playlist_.collection_items_by_id(1).count()); + EXPECT_EQ(1, playlist_.collection_items_by_id(2).count()); + + playlist_.removeRow(1); // item_two + EXPECT_EQ(2, playlist_.collection_items_by_id(1).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(2).count()); + + playlist_.removeRow(1); // item_three + EXPECT_EQ(1, playlist_.collection_items_by_id(1).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(2).count()); + + playlist_.removeRow(0); // item_one + EXPECT_EQ(0, playlist_.collection_items_by_id(1).count()); + EXPECT_EQ(0, playlist_.collection_items_by_id(2).count()); + +} + + +} // namespace diff --git a/tests/src/resources_env.h b/tests/src/resources_env.h new file mode 100644 index 00000000..ccfaef02 --- /dev/null +++ b/tests/src/resources_env.h @@ -0,0 +1,37 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef RESOURCES_ENV_H +#define RESOURCES_ENV_H + +#include + +#include + +class ResourcesEnvironment : public ::testing::Environment { +public: + void SetUp() { + Q_INIT_RESOURCE(data); + Q_INIT_RESOURCE(translations); + Q_INIT_RESOURCE(testdata); + } +}; + +#endif // RESOURCES_ENV_H diff --git a/tests/src/song_test.cpp b/tests/src/song_test.cpp new file mode 100644 index 00000000..579e7034 --- /dev/null +++ b/tests/src/song_test.cpp @@ -0,0 +1,147 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2019, Jonas Kvinge + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "config.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "core/song.h" + +#include "tagreader.h" +#include "test_utils.h" + +namespace { + +class SongTest : public ::testing::Test { + protected: + static void SetUpTestCase() { + // Return something from uninteresting mock functions. + testing::DefaultValue::Set("foobarbaz"); + } + + static Song ReadSongFromFile(const QString& filename) { + TagReader tag_reader; + Song song; + ::pb::tagreader::SongMetadata pb_song; + + // We need to init protobuf object from a Song object, to have default values initialized correctly. + song.ToProtobuf(&pb_song); + tag_reader.ReadFile(filename, &pb_song); + song.InitFromProtobuf(pb_song); + return song; + } + + static void WriteSongToFile(const Song& song, const QString& filename) { + TagReader tag_reader; + ::pb::tagreader::SongMetadata pb_song; + song.ToProtobuf(&pb_song); + tag_reader.SaveFile(filename, pb_song); + } + +}; + +TEST_F(SongTest, TestAudioFileTagging) { + + const QStringList files_to_test = QStringList() << ":/audio/strawberry.wav" + << ":/audio/strawberry.flac" + << ":/audio/strawberry.wv" + << ":/audio/strawberry.oga" + << ":/audio/strawberry.opus" + << ":/audio/strawberry.spx" + << ":/audio/strawberry.aif" + << ":/audio/strawberry.m4a" + << ":/audio/strawberry.mp4" + << ":/audio/strawberry.mp3" + << ":/audio/strawberry.asf"; + + for (const QString& test_filename : files_to_test) { + + TemporaryResource r(test_filename); + Song song = ReadSongFromFile(r.fileName()); + + // Compare files + QFile orig_file(test_filename); + orig_file.open(QIODevice::ReadOnly); + EXPECT_TRUE(orig_file.isOpen()); + QByteArray orig_file_data = orig_file.readAll(); + + QFile temp_file(r.fileName()); + temp_file.open(QIODevice::ReadOnly); + EXPECT_TRUE(temp_file.isOpen()); + QByteArray temp_file_data = temp_file.readAll(); + + EXPECT_TRUE(!orig_file_data.isEmpty()); + EXPECT_TRUE(!temp_file_data.isEmpty()); + EXPECT_TRUE(orig_file_data == temp_file_data); + + if (test_filename.contains(QRegExp(".*\\.wav$"))) continue; + + // Write tags + song.set_title("strawberry title"); + song.set_artist("strawberry artist"); + song.set_album("strawberry album"); + song.set_albumartist("strawberry album artist"); + song.set_composer("strawberry composer"); + song.set_performer("strawberry performer"); + song.set_grouping("strawberry grouping"); + song.set_genre("strawberry genre"); + song.set_comment("strawberry comment"); + song.set_track(12); + song.set_disc(1234); + song.set_year(2019); + WriteSongToFile(song, r.fileName()); + + // Read tags + Song new_song = ReadSongFromFile(r.fileName()); + EXPECT_EQ("strawberry title", new_song.title()); + EXPECT_EQ("strawberry artist", new_song.artist()); + EXPECT_EQ("strawberry album", new_song.album()); + if (!test_filename.contains(QRegExp(".*\\.aif$")) && !test_filename.contains(QRegExp(".*\\.asf$"))) { + EXPECT_EQ("strawberry album artist", new_song.albumartist()); + EXPECT_EQ("strawberry composer", new_song.composer()); + if (!test_filename.contains(QRegExp(".*\\.mp4$")) && !test_filename.contains(QRegExp(".*\\.m4a$"))) { + EXPECT_EQ("strawberry performer", new_song.performer()); + } + EXPECT_EQ("strawberry grouping", new_song.grouping()); + EXPECT_EQ(1234, new_song.disc()); + } + EXPECT_EQ("strawberry genre", new_song.genre()); + if (!test_filename.contains(QRegExp(".*\\.asf$"))) { + EXPECT_EQ("strawberry comment", new_song.comment()); + } + EXPECT_EQ(12, new_song.track()); + EXPECT_EQ(2019, new_song.year()); + + } + +} + +} // namespace diff --git a/tests/src/songplaylistitem_test.cpp b/tests/src/songplaylistitem_test.cpp new file mode 100644 index 00000000..ce96cd17 --- /dev/null +++ b/tests/src/songplaylistitem_test.cpp @@ -0,0 +1,79 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include "test_utils.h" + +#include "playlist/songplaylistitem.h" + +namespace { + +class SongPlaylistItemTest : public ::testing::TestWithParam { + protected: + SongPlaylistItemTest() : temp_file_(GetParam()) {} + + void SetUp() { + // SongPlaylistItem::Url() checks if the file exists, so we need a real file + temp_file_.open(); + + absolute_file_name_ = QFileInfo(temp_file_.fileName()).absoluteFilePath(); + + song_.Init("Title", "Artist", "Album", 123); + song_.set_url(QUrl::fromLocalFile(absolute_file_name_)); + + item_.reset(new SongPlaylistItem(song_)); + + if (!absolute_file_name_.startsWith('/')) + absolute_file_name_.prepend('/'); + } + + Song song_; + QTemporaryFile temp_file_; + QString absolute_file_name_; + std::unique_ptr item_; +}; + +INSTANTIATE_TEST_CASE_P(RealFiles, SongPlaylistItemTest, testing::Values( + "normalfile.flac", + "file with spaces.flac", + "file with # hash.flac", + "file with ? question.flac" +)); + +TEST_P(SongPlaylistItemTest, Url) { + QUrl expected; + expected.setScheme("file"); + expected.setPath(absolute_file_name_); + + EXPECT_EQ(expected, item_->Url()); +} + + +} //namespace + diff --git a/tests/src/sqlite_test.cpp b/tests/src/sqlite_test.cpp new file mode 100644 index 00000000..647da236 --- /dev/null +++ b/tests/src/sqlite_test.cpp @@ -0,0 +1,16 @@ +#include +#include + +TEST(SqliteTest, FTS3SupportEnabled) { + + sqlite3* db = nullptr; + int rc = sqlite3_open(":memory:", &db); + ASSERT_EQ(0, rc); + + char* errmsg = nullptr; + rc = sqlite3_exec(db, "CREATE VIRTUAL TABLE foo USING fts3(content, TEXT)", nullptr, nullptr, &errmsg); + ASSERT_EQ(0, rc) << errmsg; + + sqlite3_close(db); + +} diff --git a/tests/src/test_utils.cpp b/tests/src/test_utils.cpp new file mode 100644 index 00000000..560d659a --- /dev/null +++ b/tests/src/test_utils.cpp @@ -0,0 +1,87 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include "test_utils.h" + +#include +#include +#include +#include +#include +#include +#include + +std::ostream& operator<<(std::ostream& stream, const QString& str) { + stream << str.toStdString(); + return stream; +} + +std::ostream& operator <<(std::ostream& stream, const QUrl& url) { + stream << url.toString().toStdString(); + return stream; +} + +std::ostream& operator <<(std::ostream& stream, const QNetworkRequest& req) { + stream << req.url().toString().toStdString(); + return stream; +} + +std::ostream& operator <<(std::ostream& stream, const QVariant& var) { + stream << var.toString().toStdString(); + return stream; +} + +void PrintTo(const ::QString& str, std::ostream& os) { + os << str.toStdString(); +} + +void PrintTo(const ::QVariant& var, std::ostream& os) { + os << var.toString().toStdString(); +} + +void PrintTo(const ::QUrl& url, std::ostream& os) { + os << url.toString().toStdString(); +} + +TemporaryResource::TemporaryResource(const QString& filename) { + + setFileTemplate(QDir::tempPath() + "/strawberry_test-XXXXXX." + filename.section('.', -1, -1)); + open(); + + QFile resource(filename); + resource.open(QIODevice::ReadOnly); + write(resource.readAll()); + + reset(); + +} + +TestQObject::TestQObject(QObject* parent) + : QObject(parent), + invoked_(0) { +} + +void TestQObject::Emit() { + emit Emitted(); +} + +void TestQObject::Invoke() { + ++invoked_; +} diff --git a/tests/src/test_utils.h b/tests/src/test_utils.h new file mode 100644 index 00000000..fee8b31c --- /dev/null +++ b/tests/src/test_utils.h @@ -0,0 +1,87 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +#include + +#include +#include +#include + +class QNetworkRequest; +class QString; +class QUrl; +class QVariant; + +std::ostream& operator <<(std::ostream& stream, const QString& str); +std::ostream& operator <<(std::ostream& stream, const QVariant& var); +std::ostream& operator <<(std::ostream& stream, const QUrl& url); +std::ostream& operator <<(std::ostream& stream, const QNetworkRequest& req); + +template +std::ostream& operator <<(std::ostream& stream, const QList& list) { + stream << "QList("; + foreach (const T& item, list) { + stream << item << ","; + } + stream << ")"; + return stream; +} + +void PrintTo(const ::QString& str, std::ostream& os); +void PrintTo(const ::QVariant& var, std::ostream& os); +void PrintTo(const ::QUrl& url, std::ostream& os); + +#define EXPOSE_SIGNAL0(n) \ + void Emit##n() { emit n(); } +#define EXPOSE_SIGNAL1(n, t1) \ + void Emit##n(const t1& a1) { emit n(a1); } +#define EXPOSE_SIGNAL2(n, t1, t2) \ + void Emit##n(const t1& a1, const t2& a2) { emit n(a1, a2); } + +Q_DECLARE_METATYPE(QModelIndex); + +class TemporaryResource : public QTemporaryFile { +public: + TemporaryResource(const QString& filename); +}; + +class TestQObject : public QObject { + Q_OBJECT + public: + TestQObject(QObject* parent = 0); + + void Emit(); + + int invoked() const { return invoked_; } + + signals: + void Emitted(); + + public slots: + void Invoke(); + + private: + int invoked_; +}; + +#endif // TEST_UTILS_H diff --git a/tests/src/testobjectdecorators.cpp b/tests/src/testobjectdecorators.cpp new file mode 100644 index 00000000..0cd540ab --- /dev/null +++ b/tests/src/testobjectdecorators.cpp @@ -0,0 +1,30 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#define protected public +#include +#undef protected + +#include "testobjectdecorators.h" + + +void TestObjectDecorators::initStyleOption(QProgressBar* self, QStyleOptionProgressBar* opt) { + self->initStyleOption(opt); +} diff --git a/tests/src/testobjectdecorators.h b/tests/src/testobjectdecorators.h new file mode 100644 index 00000000..f9d35d95 --- /dev/null +++ b/tests/src/testobjectdecorators.h @@ -0,0 +1,36 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#ifndef TESTOBJECTDECORATORS_H +#define TESTOBJECTDECORATORS_H + +#include + +class QProgressBar; +class QStyleOptionProgressBar; + +class TestObjectDecorators : public QObject { + Q_OBJECT + +public slots: + void initStyleOption(QProgressBar* self, QStyleOptionProgressBar* opt); +}; + +#endif // TESTOBJECTDECORATORS_H diff --git a/tests/src/utilities_test.cpp b/tests/src/utilities_test.cpp new file mode 100644 index 00000000..af4a9287 --- /dev/null +++ b/tests/src/utilities_test.cpp @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * This file was part of Clementine. + * Copyright 2010, David Sansome + * + * Strawberry is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Strawberry is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Strawberry. If not, see . + * + */ + +#include + +#include +#include +#include + +#include "test_utils.h" +#include "core/utilities.h" + +TEST(UtilitiesTest, HmacFunctions) { + QString key("key"); + QString data("The quick brown fox jumps over the lazy dog"); + // Test Hmac Md5 + QByteArray result_hash_md5 = Utilities::HmacMd5(key.toLocal8Bit(), data.toLocal8Bit()).toHex(); + bool result_md5 = result_hash_md5 == QString("80070713463e7749b90c2dc24911e275"); + EXPECT_TRUE(result_md5); + // Test Hmac Sha256 + QByteArray result_hash_sha256 = Utilities::HmacSha256(key.toLocal8Bit(), data.toLocal8Bit()).toHex(); + bool result_sha256 = result_hash_sha256 == QString("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"); + EXPECT_TRUE(result_sha256); +} + +TEST(UtilitiesTest, ParseRFC822DateTim) { + QDateTime result_DateTime = Utilities::ParseRFC822DateTime(QString("22 Feb 2008 00:16:17 GMT")); + EXPECT_TRUE(result_DateTime.isValid()); + result_DateTime = Utilities::ParseRFC822DateTime(QString("Thu, 13 Dec 2012 13:27:52 +0000")); + EXPECT_TRUE(result_DateTime.isValid()); + result_DateTime = Utilities::ParseRFC822DateTime(QString("Mon, 12 March 2012 20:00:00 +0100")); + EXPECT_TRUE(result_DateTime.isValid()); +}