diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de1001caf..0456f6a44 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,7 @@ set(CLEMENTINE-SOURCES xspfparser.cpp globalshortcuts/globalshortcuts.cpp fixlastfm.cpp + backgroundthread.cpp ) # Header files that have Q_OBJECT in diff --git a/src/albumcovermanager.cpp b/src/albumcovermanager.cpp index 208ea6b92..c2129d4b0 100644 --- a/src/albumcovermanager.cpp +++ b/src/albumcovermanager.cpp @@ -22,7 +22,7 @@ const char* AlbumCoverManager::kSettingsGroup = "CoverManager"; AlbumCoverManager::AlbumCoverManager(QNetworkAccessManager* network, QWidget *parent) : QDialog(parent), constructed_(false), - cover_loader_(new BackgroundThread(this)), + cover_loader_(new BackgroundThreadImplementation(this)), cover_fetcher_(new AlbumCoverFetcher(network, this)), artist_icon_(":/artist.png"), all_artists_icon_(":/album.png"), @@ -89,7 +89,7 @@ AlbumCoverManager::AlbumCoverManager(QNetworkAccessManager* network, QWidget *pa ui_.splitter->setSizes(QList() << 200 << width() - 200); } - cover_loader_->start(); + cover_loader_->Start(); constructed_ = true; } @@ -102,7 +102,7 @@ void AlbumCoverManager::CoverLoaderInitialised() { SLOT(CoverImageLoaded(quint64,QImage))); } -void AlbumCoverManager::SetBackend(boost::shared_ptr backend) { +void AlbumCoverManager::SetBackend(boost::shared_ptr backend) { backend_ = backend; if (isVisible()) @@ -166,7 +166,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem* current) { // Get the list of albums. How we do it depends on what thing we have // selected in the artist list. - LibraryBackend::AlbumList albums; + LibraryBackendInterface::AlbumList albums; switch (current->type()) { case Various_Artists: albums = backend_->GetCompilationAlbums(); break; case Specific_Artist: albums = backend_->GetAlbumsByArtist(current->text()); break; @@ -174,7 +174,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem* current) { default: albums = backend_->GetAllAlbums(); break; } - foreach (const LibraryBackend::Album& info, albums) { + foreach (const LibraryBackendInterface::Album& info, albums) { // Don't show songs without an album, obviously if (info.album_name.isEmpty()) continue; diff --git a/src/albumcovermanager.h b/src/albumcovermanager.h index 3dafb9779..b7cec8cc0 100644 --- a/src/albumcovermanager.h +++ b/src/albumcovermanager.h @@ -10,7 +10,7 @@ #include "backgroundthread.h" #include "albumcoverloader.h" -class LibraryBackend; +class LibraryBackendInterface; class AlbumCoverFetcher; class QNetworkAccessManager; @@ -26,7 +26,7 @@ class AlbumCoverManager : public QDialog { void Reset(); public slots: - void SetBackend(boost::shared_ptr backend); + void SetBackend(boost::shared_ptr backend); protected: void showEvent(QShowEvent *); @@ -70,7 +70,7 @@ class AlbumCoverManager : public QDialog { bool constructed_; Ui::CoverManager ui_; - boost::shared_ptr backend_; + boost::shared_ptr backend_; QAction* filter_all_; QAction* filter_with_covers_; diff --git a/src/backgroundthread.cpp b/src/backgroundthread.cpp new file mode 100644 index 000000000..ce8a7cbf9 --- /dev/null +++ b/src/backgroundthread.cpp @@ -0,0 +1,21 @@ +#include "backgroundthread.h" + +int BackgroundThreadBase::SetIOPriority() { +#ifdef Q_OS_LINUX + return syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, gettid(), + 4 | io_priority_ << IOPRIO_CLASS_SHIFT); +#elif defined(Q_OS_DARWIN) + return setpriority(PRIO_DARWIN_THREAD, 0, + io_priority_ == IOPRIO_CLASS_IDLE ? PRIO_DARWIN_BG : 0); +#else + return 0; +#endif +} + +int BackgroundThreadBase::gettid() { +#ifdef Q_OS_LINUX + return syscall(SYS_gettid); +#else + return 0; +#endif +} diff --git a/src/backgroundthread.h b/src/backgroundthread.h index e88c44cb6..9c2d100f7 100644 --- a/src/backgroundthread.h +++ b/src/backgroundthread.h @@ -13,11 +13,29 @@ # include #endif +// These classes are a bit confusing because they're trying to do so many +// things: +// * Run a worker in a background thread +// * ... or maybe run it in the same thread if we're in a test +// * Use interfaces throughout, so the implementations can be mocked +// * Create concrete implementations of the interfaces when threads start +// +// The types you should use throughout your header files are: +// BackgroundThread +// BackgroundThreadFactory +// +// You should allow callers to set their own factory (which might return mocks +// of your interface), and default to using a: +// BackgroundThreadFactoryImplementation + + +// This is the base class. We need one because moc doesn't like templated +// classes. This also deals with anything that doesn't depend on the type of +// the worker. class BackgroundThreadBase : public QThread { Q_OBJECT public: BackgroundThreadBase(QObject* parent = 0) : QThread(parent), io_priority_(IOPRIO_CLASS_NONE) {} - virtual ~BackgroundThreadBase() {} // Borrowed from schedutils enum IoPriority { @@ -28,14 +46,16 @@ class BackgroundThreadBase : public QThread { }; void set_io_priority(IoPriority priority) { io_priority_ = priority; } + void set_cpu_priority(QThread::Priority priority) { cpu_priority_ = priority; } + + virtual void Start() { QThread::start(cpu_priority_); } signals: void Initialised(); protected: - // Borrowed from schedutils - static inline int ioprio_set(int which, int who, int ioprio); - static inline int gettid(); + int SetIOPriority(); + static int gettid(); enum { IOPRIO_WHO_PROCESS = 1, @@ -45,31 +65,61 @@ class BackgroundThreadBase : public QThread { static const int IOPRIO_CLASS_SHIFT = 13; IoPriority io_priority_; + QThread::Priority cpu_priority_; }; -template +// This is the templated class that stores and returns the worker object. +template class BackgroundThread : public BackgroundThreadBase { public: BackgroundThread(QObject* parent = 0); - virtual ~BackgroundThread(); + ~BackgroundThread(); - boost::shared_ptr Worker() const { return worker_; } + boost::shared_ptr Worker() const { return worker_; } + + protected: + boost::shared_ptr worker_; +}; + +// This class actually creates an implementation of the worker object +template +class BackgroundThreadImplementation : public BackgroundThread { + public: + BackgroundThreadImplementation(QObject* parent = 0); protected: void run(); - - private: - boost::shared_ptr worker_; }; -template -BackgroundThread::BackgroundThread(QObject *parent) + +// This is a pure virtual factory for creating threads. +template +class BackgroundThreadFactory { + public: + virtual ~BackgroundThreadFactory() {} + virtual BackgroundThread* GetThread(QObject* parent) = 0; +}; + +// This implementation of the factory returns a BackgroundThread that creates +// the right derived types... +template +class BackgroundThreadFactoryImplementation : public BackgroundThreadFactory { + public: + BackgroundThread* GetThread(QObject* parent) { + return new BackgroundThreadImplementation(parent); + } +}; + + + +template +BackgroundThread::BackgroundThread(QObject *parent) : BackgroundThreadBase(parent) { } -template -BackgroundThread::~BackgroundThread() { +template +BackgroundThread::~BackgroundThread() { if (isRunning()) { quit(); if (wait(10000)) @@ -79,39 +129,27 @@ BackgroundThread::~BackgroundThread() { } } -template -void BackgroundThread::run() { -#ifdef Q_OS_LINUX - if (io_priority_ != IOPRIO_CLASS_NONE) { - ioprio_set(IOPRIO_WHO_PROCESS, gettid(), - 4 | io_priority_ << IOPRIO_CLASS_SHIFT); - } -#endif - - worker_.reset(new T); - - emit Initialised(); - exec(); - - worker_.reset(); +template +BackgroundThreadImplementation:: + BackgroundThreadImplementation(QObject* parent) + : BackgroundThread(parent) +{ } -int BackgroundThreadBase::ioprio_set(int which, int who, int ioprio) { -#ifdef Q_OS_LINUX - return syscall(SYS_ioprio_set, which, who, ioprio); -#elif defined(Q_OS_DARWIN) - return setpriority(PRIO_DARWIN_THREAD, 0, ioprio == IOPRIO_CLASS_IDLE ? PRIO_DARWIN_BG : 0); -#else - return 0; -#endif + +template +void BackgroundThreadImplementation::run() { + BackgroundThreadBase::SetIOPriority(); + + BackgroundThread::worker_.reset(new DerivedType); + + emit BackgroundThreadBase::Initialised(); + QThread::exec(); + + BackgroundThread::worker_.reset(); } -int BackgroundThreadBase::gettid() { -#ifdef Q_OS_LINUX - return syscall(SYS_gettid); -#else - return 0; -#endif -} + + #endif // BACKGROUNDTHREAD_H diff --git a/src/library.cpp b/src/library.cpp index 8258b03a0..836ec6939 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -13,31 +13,49 @@ Library::Library(EngineBase* engine, QObject* parent) : SimpleTreeModel(new LibraryItem(this), parent), engine_(engine), - backend_(new BackgroundThread(this)), - watcher_(new BackgroundThread(this)), + backend_factory_(new BackgroundThreadFactoryImplementation), + watcher_factory_(new BackgroundThreadFactoryImplementation), + backend_(NULL), + watcher_(NULL), dir_model_(new LibraryDirectoryModel(this)), + waiting_for_threads_(2), artist_icon_(":artist.png"), album_icon_(":album.png"), no_cover_icon_(":nocover.png") { root_->lazy_loaded = true; - - connect(backend_, SIGNAL(Initialised()), SLOT(BackendInitialised())); - connect(watcher_, SIGNAL(Initialised()), SLOT(WatcherInitialised())); - waiting_for_threads_ = 2; } Library::~Library() { delete root_; } +void Library::set_backend_factory(BackgroundThreadFactory* factory) { + backend_factory_.reset(factory); +} + +void Library::set_watcher_factory(BackgroundThreadFactory* factory) { + watcher_factory_.reset(factory); +} + +void Library::Init() { + backend_ = backend_factory_->GetThread(this); + watcher_ = watcher_factory_->GetThread(this); + + connect(backend_, SIGNAL(Initialised()), SLOT(BackendInitialised())); + connect(watcher_, SIGNAL(Initialised()), SLOT(WatcherInitialised())); +} + void Library::StartThreads() { Q_ASSERT(waiting_for_threads_); + Q_ASSERT(backend_); + Q_ASSERT(watcher_); - backend_->start(); + backend_->Start(); watcher_->set_io_priority(BackgroundThreadBase::IOPRIO_CLASS_IDLE); - watcher_->start(QThread::IdlePriority); + watcher_->set_cpu_priority(QThread::IdlePriority); + watcher_->Start(); } void Library::BackendInitialised() { @@ -375,7 +393,7 @@ void Library::LazyPopulate(LibraryItem* item) { switch (item->type) { case LibraryItem::Type_CompilationArtist: - foreach (const LibraryBackend::Album& album, + foreach (const LibraryBackendInterface::Album& album, backend_->Worker()->GetCompilationAlbums(query_options_)) CreateAlbumNode(false, album.album_name, item, true, album.art_automatic, album.art_manual, album.artist); break; @@ -386,7 +404,7 @@ void Library::LazyPopulate(LibraryItem* item) { break; case LibraryItem::Type_Artist: - foreach (const LibraryBackend::Album& album, + foreach (const LibraryBackendInterface::Album& album, backend_->Worker()->GetAlbumsByArtist(item->key, query_options_)) CreateAlbumNode(false, album.album_name, item, false, album.art_automatic, album.art_manual, album.artist); break; diff --git a/src/library.h b/src/library.h index adb8cab8c..fcaf212ee 100644 --- a/src/library.h +++ b/src/library.h @@ -13,6 +13,8 @@ #include "libraryitem.h" #include "simpletreemodel.h" +#include + class LibraryDirectoryModel; class Library : public SimpleTreeModel { @@ -29,10 +31,15 @@ class Library : public SimpleTreeModel { Role_Artist, }; + // Useful for tests. The library takes ownership. + void set_backend_factory(BackgroundThreadFactory* factory); + void set_watcher_factory(BackgroundThreadFactory* factory); + + void Init(); void StartThreads(); LibraryDirectoryModel* GetDirectoryModel() const { return dir_model_; } - boost::shared_ptr GetBackend() const { return backend_->Worker(); } + boost::shared_ptr GetBackend() const { return backend_->Worker(); } // Get information about the library void GetChildSongs(LibraryItem* item, QList* urls, SongList* songs) const; @@ -52,7 +59,7 @@ class Library : public SimpleTreeModel { void ScanStarted(); void ScanFinished(); - void BackendReady(boost::shared_ptr backend); + void BackendReady(boost::shared_ptr backend); public slots: void SetFilterAge(int age); @@ -96,7 +103,9 @@ class Library : public SimpleTreeModel { private: EngineBase* engine_; - BackgroundThread* backend_; + boost::scoped_ptr > backend_factory_; + boost::scoped_ptr > watcher_factory_; + BackgroundThread* backend_; BackgroundThread* watcher_; LibraryDirectoryModel* dir_model_; diff --git a/src/librarybackend.cpp b/src/librarybackend.cpp index 278ca77d5..0c2518d78 100644 --- a/src/librarybackend.cpp +++ b/src/librarybackend.cpp @@ -100,8 +100,13 @@ void LibraryBackend::SqliteLike(sqlite3_context* context, int argc, sqlite3_valu } } +LibraryBackendInterface::LibraryBackendInterface(QObject *parent) + : QObject(parent) +{ +} + LibraryBackend::LibraryBackend(QObject* parent, const QString& database_name) - : QObject(parent), + : LibraryBackendInterface(parent), injected_database_name_(database_name) { QSettings s; diff --git a/src/librarybackend.h b/src/librarybackend.h index beb7b89fc..ab9312302 100644 --- a/src/librarybackend.h +++ b/src/librarybackend.h @@ -15,11 +15,11 @@ #include "gtest/gtest_prod.h" -class LibraryBackend : public QObject { +class LibraryBackendInterface : public QObject { Q_OBJECT public: - LibraryBackend(QObject* parent = 0, const QString& database_name = QString()); + LibraryBackendInterface(QObject* parent = 0); struct Album { QString artist; @@ -30,6 +30,63 @@ class LibraryBackend : public QObject { }; typedef QList AlbumList; + // Get a list of directories in the library. Emits DirectoriesDiscovered. + virtual void LoadDirectoriesAsync() = 0; + + // Counts the songs in the library. Emits TotalSongCountUpdated + virtual void UpdateTotalSongCountAsync() = 0; + + virtual SongList FindSongsInDirectory(int id) = 0; + + virtual QStringList GetAllArtists(const QueryOptions& opt = QueryOptions()) = 0; + virtual SongList GetSongs(const QString& artist, const QString& album, const QueryOptions& opt = QueryOptions()) = 0; + + virtual bool HasCompilations(const QueryOptions& opt = QueryOptions()) = 0; + virtual SongList GetCompilationSongs(const QString& album, const QueryOptions& opt = QueryOptions()) = 0; + + virtual AlbumList GetAllAlbums(const QueryOptions& opt = QueryOptions()) = 0; + virtual AlbumList GetAlbumsByArtist(const QString& artist, const QueryOptions& opt = QueryOptions()) = 0; + virtual AlbumList GetCompilationAlbums(const QueryOptions& opt = QueryOptions()) = 0; + + virtual void UpdateManualAlbumArtAsync(const QString& artist, const QString& album, const QString& art) = 0; + virtual Album GetAlbumArt(const QString& artist, const QString& album) = 0; + + virtual Song GetSongById(int id) = 0; + + virtual void AddDirectory(const QString& path) = 0; + virtual void RemoveDirectory(const Directory& dir) = 0; + + virtual void UpdateCompilationsAsync() = 0; + + public slots: + virtual void LoadDirectories() = 0; + virtual void UpdateTotalSongCount() = 0; + virtual void AddOrUpdateSongs(const SongList& songs) = 0; + virtual void UpdateMTimesOnly(const SongList& songs) = 0; + virtual void DeleteSongs(const SongList& songs) = 0; + virtual void UpdateCompilations() = 0; + virtual void UpdateManualAlbumArt(const QString& artist, const QString& album, const QString& art) = 0; + virtual void ForceCompilation(const QString& artist, const QString& album, bool on) = 0; + + signals: + void Error(const QString& message); + + void DirectoriesDiscovered(const DirectoryList& directories); + void DirectoriesDeleted(const DirectoryList& directories); + + void SongsDiscovered(const SongList& songs); + void SongsDeleted(const SongList& songs); + + void TotalSongCountUpdated(int total); +}; + + +class LibraryBackend : public LibraryBackendInterface { + Q_OBJECT + + public: + LibraryBackend(QObject* parent = 0, const QString& database_name = QString()); + static const int kSchemaVersion; // This actually refers to the location of the sqlite database @@ -73,17 +130,6 @@ class LibraryBackend : public QObject { void UpdateManualAlbumArt(const QString& artist, const QString& album, const QString& art); void ForceCompilation(const QString& artist, const QString& album, bool on); - signals: - void Error(const QString& message); - - void DirectoriesDiscovered(const DirectoryList& directories); - void DirectoriesDeleted(const DirectoryList& directories); - - void SongsDiscovered(const SongList& songs); - void SongsDeleted(const SongList& songs); - - void TotalSongCountUpdated(int total); - private: struct CompilationInfo { CompilationInfo() : has_samplers(false), has_not_samplers(false) {} diff --git a/src/librarydirectorymodel.cpp b/src/librarydirectorymodel.cpp index 8e585c764..216261a4c 100644 --- a/src/librarydirectorymodel.cpp +++ b/src/librarydirectorymodel.cpp @@ -7,7 +7,7 @@ LibraryDirectoryModel::LibraryDirectoryModel(QObject* parent) { } -void LibraryDirectoryModel::SetBackend(boost::shared_ptr backend) { +void LibraryDirectoryModel::SetBackend(boost::shared_ptr backend) { backend_ = backend; connect(backend_.get(), SIGNAL(DirectoriesDiscovered(DirectoryList)), SLOT(DirectoriesDiscovered(DirectoryList))); diff --git a/src/librarydirectorymodel.h b/src/librarydirectorymodel.h index b3692addc..cfb95e771 100644 --- a/src/librarydirectorymodel.h +++ b/src/librarydirectorymodel.h @@ -8,7 +8,7 @@ #include "directory.h" -class LibraryBackend; +class LibraryBackendInterface; class LibraryDirectoryModel : public QStandardItemModel { Q_OBJECT @@ -16,7 +16,7 @@ class LibraryDirectoryModel : public QStandardItemModel { public: LibraryDirectoryModel(QObject* parent = 0); - void SetBackend(boost::shared_ptr backend); + void SetBackend(boost::shared_ptr backend); bool IsBackendReady() const { return backend_; } // To be called by GUIs @@ -35,7 +35,7 @@ class LibraryDirectoryModel : public QStandardItemModel { static const int kIdRole = Qt::UserRole + 1; QIcon dir_icon_; - boost::shared_ptr backend_; + boost::shared_ptr backend_; }; #endif // LIBRARYDIRECTORYMODEL_H diff --git a/src/librarywatcher.h b/src/librarywatcher.h index 50e7c9b2d..d5b2d7ed3 100644 --- a/src/librarywatcher.h +++ b/src/librarywatcher.h @@ -14,7 +14,7 @@ class QFileSystemWatcher; class QTimer; -class LibraryBackend; +class LibraryBackendInterface; class LibraryWatcher : public QObject { Q_OBJECT @@ -22,7 +22,7 @@ class LibraryWatcher : public QObject { public: LibraryWatcher(QObject* parent = 0); - void SetBackend(boost::shared_ptr backend) { backend_ = backend; } + void SetBackend(boost::shared_ptr backend) { backend_ = backend; } void SetEngine(EngineBase* engine) { engine_ = engine; } // TODO: shared_ptr signals: @@ -51,7 +51,7 @@ class LibraryWatcher : public QObject { private: EngineBase* engine_; - boost::shared_ptr backend_; + boost::shared_ptr backend_; QFileSystemWatcher* fs_watcher_; QTimer* rescan_timer_; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0e2ab8ea2..468e2d963 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -179,8 +179,8 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent) connect(library_, SIGNAL(TotalSongCountUpdated(int)), ui_.library_view, SLOT(TotalSongCountUpdated(int))); connect(library_, SIGNAL(ScanStarted()), SLOT(LibraryScanStarted())); connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished())); - connect(library_, SIGNAL(BackendReady(boost::shared_ptr)), - cover_manager_, SLOT(SetBackend(boost::shared_ptr))); + connect(library_, SIGNAL(BackendReady(boost::shared_ptr)), + cover_manager_, SLOT(SetBackend(boost::shared_ptr))); // Age filters QActionGroup* filter_age_group = new QActionGroup(this); @@ -311,6 +311,7 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent) show(); } + library_->Init(); library_->StartThreads(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 763dabc70..9ced4ca02 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -31,10 +31,12 @@ target_link_libraries(gmock gtest) set(TESTUTILS-SOURCES test_utils.cpp mock_networkaccessmanager.cpp - mock_taglib.cpp) + mock_taglib.cpp +) set(TESTUTILS-MOC-HEADERS - mock_networkaccessmanager.h) + mock_networkaccessmanager.h +) qt4_wrap_cpp(TESTUTILS-SOURCES-MOC ${TESTUTILS-MOC-HEADERS}) @@ -45,6 +47,10 @@ add_custom_target(test echo "Running tests" WORKING_DIRECTORY ${CURRENT_BINARY_DIR} ) +add_custom_target(build_tests + WORKING_DIRECTORY ${CURRENT_BINARY_DIR} +) +add_dependencies(test build_tests) # Given a file foo_test.cpp, creates a target foo_test and adds it to the test target. macro(add_test_file test_source) @@ -57,7 +63,7 @@ macro(add_test_file test_source) target_link_libraries(${TEST_NAME} gmock clementine_lib test_utils) add_custom_command(TARGET test POST_BUILD COMMAND ./${TEST_NAME}) - add_dependencies(test ${TEST_NAME}) + add_dependencies(build_tests ${TEST_NAME}) endmacro (add_test_file) @@ -66,3 +72,4 @@ add_test_file(song_test.cpp) add_test_file(librarybackend_test.cpp) add_test_file(albumcoverfetcher_test.cpp) add_test_file(xspfparser_test.cpp) +add_test_file(library_test.cpp) diff --git a/tests/library_test.cpp b/tests/library_test.cpp new file mode 100644 index 000000000..86b7198db --- /dev/null +++ b/tests/library_test.cpp @@ -0,0 +1,46 @@ +#include "test_utils.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "library.h" +#include "backgroundthread.h" +#include "mock_backgroundthread.h" +#include "mock_librarybackend.h" + +#include + +#include +#include +#include + +using ::testing::_; +using ::testing::Return; + +class LibraryTest : public ::testing::Test { + protected: + virtual void SetUp() { + library_.reset(new Library(NULL, NULL)); + library_->set_backend_factory( + new FakeBackgroundThreadFactory); + library_->set_watcher_factory( + new FakeBackgroundThreadFactory); + + library_->Init(); + + backend_ = static_cast(library_->GetBackend().get()); + } + + boost::scoped_ptr library_; + MockLibraryBackend* backend_; +}; + +TEST_F(LibraryTest, TestInitialisation) { + EXPECT_CALL(*backend_, LoadDirectoriesAsync()); + EXPECT_CALL(*backend_, UpdateTotalSongCountAsync()); + EXPECT_CALL(*backend_, HasCompilations(_)) + .WillOnce(Return(false)); + EXPECT_CALL(*backend_, GetAllArtists(_)) + .WillOnce(Return(QStringList())); + + library_->StartThreads(); +} diff --git a/tests/main.cpp b/tests/main.cpp index 199f5a51a..165212f85 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,13 +1,16 @@ #include +#include + #include "resources_env.h" #include "metatypes_env.h" int main(int argc, char** argv) { testing::InitGoogleMock(&argc, argv); - testing::AddGlobalTestEnvironment(new ResourcesEnvironment); testing::AddGlobalTestEnvironment(new MetatypesEnvironment); + QApplication a(argc, argv); + testing::AddGlobalTestEnvironment(new ResourcesEnvironment); return RUN_ALL_TESTS(); } diff --git a/tests/mock_backgroundthread.h b/tests/mock_backgroundthread.h new file mode 100644 index 000000000..84056ce2c --- /dev/null +++ b/tests/mock_backgroundthread.h @@ -0,0 +1,28 @@ +#ifndef MOCK_BACKGROUNDTHREAD_H +#define MOCK_BACKGROUNDTHREAD_H + +#include "backgroundthread.h" + +template +class FakeBackgroundThread : public BackgroundThread { + public: + FakeBackgroundThread(QObject* parent) : BackgroundThread(parent) { + // We have to create the actual worker here instead of in Start() so that + // tests can set mock expectations on it before Initialised is emitted. + BackgroundThread::worker_.reset(new DerivedType); + } + + void Start() { + emit BackgroundThreadBase::Initialised(); + } +}; + +template +class FakeBackgroundThreadFactory : public BackgroundThreadFactory { + public: + BackgroundThread* GetThread(QObject* parent) { + return new FakeBackgroundThread(parent); + } +}; + +#endif diff --git a/tests/mock_librarybackend.h b/tests/mock_librarybackend.h new file mode 100644 index 000000000..41bfa8207 --- /dev/null +++ b/tests/mock_librarybackend.h @@ -0,0 +1,58 @@ +#ifndef MOCK_LIBRARYBACKEND_H +#define MOCK_LIBRARYBACKEND_H + +#include "librarybackend.h" + +class MockLibraryBackend : public LibraryBackendInterface { + public: + MOCK_METHOD0(LoadDirectoriesAsync, + void()); + MOCK_METHOD0(UpdateTotalSongCountAsync, + void()); + MOCK_METHOD1(FindSongsInDirectory, + SongList(int id)); + MOCK_METHOD1(GetAllArtists, + QStringList(const QueryOptions& opt)); + MOCK_METHOD3(GetSongs, + SongList(const QString& artist, const QString& album, const QueryOptions& opt)); + MOCK_METHOD1(HasCompilations, + bool(const QueryOptions& opt)); + MOCK_METHOD2(GetCompilationSongs, + SongList(const QString& album, const QueryOptions& opt)); + MOCK_METHOD1(GetAllAlbums, + AlbumList(const QueryOptions& opt)); + MOCK_METHOD2(GetAlbumsByArtist, + AlbumList(const QString& artist, const QueryOptions& opt)); + MOCK_METHOD1(GetCompilationAlbums, + AlbumList(const QueryOptions& opt)); + MOCK_METHOD3(UpdateManualAlbumArtAsync, + void(const QString& artist, const QString& album, const QString& art)); + MOCK_METHOD2(GetAlbumArt, + Album(const QString& artist, const QString& album)); + MOCK_METHOD1(GetSongById, + Song(int id)); + MOCK_METHOD1(AddDirectory, + void(const QString& path)); + MOCK_METHOD1(RemoveDirectory, + void(const Directory& dir)); + MOCK_METHOD0(UpdateCompilationsAsync, + void()); + MOCK_METHOD0(LoadDirectories, + void()); + MOCK_METHOD0(UpdateTotalSongCount, + void()); + MOCK_METHOD1(AddOrUpdateSongs, + void(const SongList& songs)); + MOCK_METHOD1(UpdateMTimesOnly, + void(const SongList& songs)); + MOCK_METHOD1(DeleteSongs, + void(const SongList& songs)); + MOCK_METHOD0(UpdateCompilations, + void()); + MOCK_METHOD3(UpdateManualAlbumArt, + void(const QString& artist, const QString& album, const QString& art)); + MOCK_METHOD3(ForceCompilation, + void(const QString& artist, const QString& album, bool on)); +}; + +#endif