Make it possible for the LibraryBackend to be mocked, and add a simple test for Library.

This commit is contained in:
David Sansome 2010-03-23 17:26:54 +00:00
parent 777cc79b95
commit 16e1deaade
18 changed files with 373 additions and 92 deletions

View File

@ -60,6 +60,7 @@ set(CLEMENTINE-SOURCES
xspfparser.cpp xspfparser.cpp
globalshortcuts/globalshortcuts.cpp globalshortcuts/globalshortcuts.cpp
fixlastfm.cpp fixlastfm.cpp
backgroundthread.cpp
) )
# Header files that have Q_OBJECT in # Header files that have Q_OBJECT in

View File

@ -22,7 +22,7 @@ const char* AlbumCoverManager::kSettingsGroup = "CoverManager";
AlbumCoverManager::AlbumCoverManager(QNetworkAccessManager* network, QWidget *parent) AlbumCoverManager::AlbumCoverManager(QNetworkAccessManager* network, QWidget *parent)
: QDialog(parent), : QDialog(parent),
constructed_(false), constructed_(false),
cover_loader_(new BackgroundThread<AlbumCoverLoader>(this)), cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this)),
cover_fetcher_(new AlbumCoverFetcher(network, this)), cover_fetcher_(new AlbumCoverFetcher(network, this)),
artist_icon_(":/artist.png"), artist_icon_(":/artist.png"),
all_artists_icon_(":/album.png"), all_artists_icon_(":/album.png"),
@ -89,7 +89,7 @@ AlbumCoverManager::AlbumCoverManager(QNetworkAccessManager* network, QWidget *pa
ui_.splitter->setSizes(QList<int>() << 200 << width() - 200); ui_.splitter->setSizes(QList<int>() << 200 << width() - 200);
} }
cover_loader_->start(); cover_loader_->Start();
constructed_ = true; constructed_ = true;
} }
@ -102,7 +102,7 @@ void AlbumCoverManager::CoverLoaderInitialised() {
SLOT(CoverImageLoaded(quint64,QImage))); SLOT(CoverImageLoaded(quint64,QImage)));
} }
void AlbumCoverManager::SetBackend(boost::shared_ptr<LibraryBackend> backend) { void AlbumCoverManager::SetBackend(boost::shared_ptr<LibraryBackendInterface> backend) {
backend_ = backend; backend_ = backend;
if (isVisible()) 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 // Get the list of albums. How we do it depends on what thing we have
// selected in the artist list. // selected in the artist list.
LibraryBackend::AlbumList albums; LibraryBackendInterface::AlbumList albums;
switch (current->type()) { switch (current->type()) {
case Various_Artists: albums = backend_->GetCompilationAlbums(); break; case Various_Artists: albums = backend_->GetCompilationAlbums(); break;
case Specific_Artist: albums = backend_->GetAlbumsByArtist(current->text()); break; case Specific_Artist: albums = backend_->GetAlbumsByArtist(current->text()); break;
@ -174,7 +174,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem* current) {
default: albums = backend_->GetAllAlbums(); break; 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 // Don't show songs without an album, obviously
if (info.album_name.isEmpty()) if (info.album_name.isEmpty())
continue; continue;

View File

@ -10,7 +10,7 @@
#include "backgroundthread.h" #include "backgroundthread.h"
#include "albumcoverloader.h" #include "albumcoverloader.h"
class LibraryBackend; class LibraryBackendInterface;
class AlbumCoverFetcher; class AlbumCoverFetcher;
class QNetworkAccessManager; class QNetworkAccessManager;
@ -26,7 +26,7 @@ class AlbumCoverManager : public QDialog {
void Reset(); void Reset();
public slots: public slots:
void SetBackend(boost::shared_ptr<LibraryBackend> backend); void SetBackend(boost::shared_ptr<LibraryBackendInterface> backend);
protected: protected:
void showEvent(QShowEvent *); void showEvent(QShowEvent *);
@ -70,7 +70,7 @@ class AlbumCoverManager : public QDialog {
bool constructed_; bool constructed_;
Ui::CoverManager ui_; Ui::CoverManager ui_;
boost::shared_ptr<LibraryBackend> backend_; boost::shared_ptr<LibraryBackendInterface> backend_;
QAction* filter_all_; QAction* filter_all_;
QAction* filter_with_covers_; QAction* filter_with_covers_;

21
src/backgroundthread.cpp Normal file
View File

@ -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
}

View File

@ -13,11 +13,29 @@
# include <sys/resource.h> # include <sys/resource.h>
#endif #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<InterfaceType>
// BackgroundThreadFactory<InterfaceType>
//
// You should allow callers to set their own factory (which might return mocks
// of your interface), and default to using a:
// BackgroundThreadFactoryImplementation<InterfaceType, DerivedType>
// 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 { class BackgroundThreadBase : public QThread {
Q_OBJECT Q_OBJECT
public: public:
BackgroundThreadBase(QObject* parent = 0) : QThread(parent), io_priority_(IOPRIO_CLASS_NONE) {} BackgroundThreadBase(QObject* parent = 0) : QThread(parent), io_priority_(IOPRIO_CLASS_NONE) {}
virtual ~BackgroundThreadBase() {}
// Borrowed from schedutils // Borrowed from schedutils
enum IoPriority { enum IoPriority {
@ -28,14 +46,16 @@ class BackgroundThreadBase : public QThread {
}; };
void set_io_priority(IoPriority priority) { io_priority_ = priority; } 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: signals:
void Initialised(); void Initialised();
protected: protected:
// Borrowed from schedutils int SetIOPriority();
static inline int ioprio_set(int which, int who, int ioprio); static int gettid();
static inline int gettid();
enum { enum {
IOPRIO_WHO_PROCESS = 1, IOPRIO_WHO_PROCESS = 1,
@ -45,31 +65,61 @@ class BackgroundThreadBase : public QThread {
static const int IOPRIO_CLASS_SHIFT = 13; static const int IOPRIO_CLASS_SHIFT = 13;
IoPriority io_priority_; IoPriority io_priority_;
QThread::Priority cpu_priority_;
}; };
template <typename T> // This is the templated class that stores and returns the worker object.
template <typename InterfaceType>
class BackgroundThread : public BackgroundThreadBase { class BackgroundThread : public BackgroundThreadBase {
public: public:
BackgroundThread(QObject* parent = 0); BackgroundThread(QObject* parent = 0);
virtual ~BackgroundThread(); ~BackgroundThread();
boost::shared_ptr<T> Worker() const { return worker_; } boost::shared_ptr<InterfaceType> Worker() const { return worker_; }
protected:
boost::shared_ptr<InterfaceType> worker_;
};
// This class actually creates an implementation of the worker object
template <typename InterfaceType, typename DerivedType>
class BackgroundThreadImplementation : public BackgroundThread<InterfaceType> {
public:
BackgroundThreadImplementation(QObject* parent = 0);
protected: protected:
void run(); void run();
private:
boost::shared_ptr<T> worker_;
}; };
template <typename T>
BackgroundThread<T>::BackgroundThread(QObject *parent) // This is a pure virtual factory for creating threads.
template <typename InterfaceType>
class BackgroundThreadFactory {
public:
virtual ~BackgroundThreadFactory() {}
virtual BackgroundThread<InterfaceType>* GetThread(QObject* parent) = 0;
};
// This implementation of the factory returns a BackgroundThread that creates
// the right derived types...
template <typename InterfaceType, typename DerivedType>
class BackgroundThreadFactoryImplementation : public BackgroundThreadFactory<InterfaceType> {
public:
BackgroundThread<InterfaceType>* GetThread(QObject* parent) {
return new BackgroundThreadImplementation<InterfaceType, DerivedType>(parent);
}
};
template <typename InterfaceType>
BackgroundThread<InterfaceType>::BackgroundThread(QObject *parent)
: BackgroundThreadBase(parent) : BackgroundThreadBase(parent)
{ {
} }
template <typename T> template <typename InterfaceType>
BackgroundThread<T>::~BackgroundThread() { BackgroundThread<InterfaceType>::~BackgroundThread() {
if (isRunning()) { if (isRunning()) {
quit(); quit();
if (wait(10000)) if (wait(10000))
@ -79,39 +129,27 @@ BackgroundThread<T>::~BackgroundThread() {
} }
} }
template <typename T> template <typename InterfaceType, typename DerivedType>
void BackgroundThread<T>::run() { BackgroundThreadImplementation<InterfaceType, DerivedType>::
#ifdef Q_OS_LINUX BackgroundThreadImplementation(QObject* parent)
if (io_priority_ != IOPRIO_CLASS_NONE) { : BackgroundThread<InterfaceType>(parent)
ioprio_set(IOPRIO_WHO_PROCESS, gettid(), {
4 | io_priority_ << IOPRIO_CLASS_SHIFT);
}
#endif
worker_.reset(new T);
emit Initialised();
exec();
worker_.reset();
} }
int BackgroundThreadBase::ioprio_set(int which, int who, int ioprio) {
#ifdef Q_OS_LINUX template <typename InterfaceType, typename DerivedType>
return syscall(SYS_ioprio_set, which, who, ioprio); void BackgroundThreadImplementation<InterfaceType, DerivedType>::run() {
#elif defined(Q_OS_DARWIN) BackgroundThreadBase::SetIOPriority();
return setpriority(PRIO_DARWIN_THREAD, 0, ioprio == IOPRIO_CLASS_IDLE ? PRIO_DARWIN_BG : 0);
#else BackgroundThread<InterfaceType>::worker_.reset(new DerivedType);
return 0;
#endif emit BackgroundThreadBase::Initialised();
QThread::exec();
BackgroundThread<InterfaceType>::worker_.reset();
} }
int BackgroundThreadBase::gettid() {
#ifdef Q_OS_LINUX
return syscall(SYS_gettid);
#else
return 0;
#endif
}
#endif // BACKGROUNDTHREAD_H #endif // BACKGROUNDTHREAD_H

View File

@ -13,31 +13,49 @@
Library::Library(EngineBase* engine, QObject* parent) Library::Library(EngineBase* engine, QObject* parent)
: SimpleTreeModel<LibraryItem>(new LibraryItem(this), parent), : SimpleTreeModel<LibraryItem>(new LibraryItem(this), parent),
engine_(engine), engine_(engine),
backend_(new BackgroundThread<LibraryBackend>(this)), backend_factory_(new BackgroundThreadFactoryImplementation<LibraryBackendInterface, LibraryBackend>),
watcher_(new BackgroundThread<LibraryWatcher>(this)), watcher_factory_(new BackgroundThreadFactoryImplementation<LibraryWatcher, LibraryWatcher>),
backend_(NULL),
watcher_(NULL),
dir_model_(new LibraryDirectoryModel(this)), dir_model_(new LibraryDirectoryModel(this)),
waiting_for_threads_(2),
artist_icon_(":artist.png"), artist_icon_(":artist.png"),
album_icon_(":album.png"), album_icon_(":album.png"),
no_cover_icon_(":nocover.png") no_cover_icon_(":nocover.png")
{ {
root_->lazy_loaded = true; root_->lazy_loaded = true;
connect(backend_, SIGNAL(Initialised()), SLOT(BackendInitialised()));
connect(watcher_, SIGNAL(Initialised()), SLOT(WatcherInitialised()));
waiting_for_threads_ = 2;
} }
Library::~Library() { Library::~Library() {
delete root_; delete root_;
} }
void Library::set_backend_factory(BackgroundThreadFactory<LibraryBackendInterface>* factory) {
backend_factory_.reset(factory);
}
void Library::set_watcher_factory(BackgroundThreadFactory<LibraryWatcher>* 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() { void Library::StartThreads() {
Q_ASSERT(waiting_for_threads_); Q_ASSERT(waiting_for_threads_);
Q_ASSERT(backend_);
Q_ASSERT(watcher_);
backend_->start(); backend_->Start();
watcher_->set_io_priority(BackgroundThreadBase::IOPRIO_CLASS_IDLE); watcher_->set_io_priority(BackgroundThreadBase::IOPRIO_CLASS_IDLE);
watcher_->start(QThread::IdlePriority); watcher_->set_cpu_priority(QThread::IdlePriority);
watcher_->Start();
} }
void Library::BackendInitialised() { void Library::BackendInitialised() {
@ -375,7 +393,7 @@ void Library::LazyPopulate(LibraryItem* item) {
switch (item->type) { switch (item->type) {
case LibraryItem::Type_CompilationArtist: case LibraryItem::Type_CompilationArtist:
foreach (const LibraryBackend::Album& album, foreach (const LibraryBackendInterface::Album& album,
backend_->Worker()->GetCompilationAlbums(query_options_)) backend_->Worker()->GetCompilationAlbums(query_options_))
CreateAlbumNode(false, album.album_name, item, true, album.art_automatic, album.art_manual, album.artist); CreateAlbumNode(false, album.album_name, item, true, album.art_automatic, album.art_manual, album.artist);
break; break;
@ -386,7 +404,7 @@ void Library::LazyPopulate(LibraryItem* item) {
break; break;
case LibraryItem::Type_Artist: case LibraryItem::Type_Artist:
foreach (const LibraryBackend::Album& album, foreach (const LibraryBackendInterface::Album& album,
backend_->Worker()->GetAlbumsByArtist(item->key, query_options_)) backend_->Worker()->GetAlbumsByArtist(item->key, query_options_))
CreateAlbumNode(false, album.album_name, item, false, album.art_automatic, album.art_manual, album.artist); CreateAlbumNode(false, album.album_name, item, false, album.art_automatic, album.art_manual, album.artist);
break; break;

View File

@ -13,6 +13,8 @@
#include "libraryitem.h" #include "libraryitem.h"
#include "simpletreemodel.h" #include "simpletreemodel.h"
#include <boost/scoped_ptr.hpp>
class LibraryDirectoryModel; class LibraryDirectoryModel;
class Library : public SimpleTreeModel<LibraryItem> { class Library : public SimpleTreeModel<LibraryItem> {
@ -29,10 +31,15 @@ class Library : public SimpleTreeModel<LibraryItem> {
Role_Artist, Role_Artist,
}; };
// Useful for tests. The library takes ownership.
void set_backend_factory(BackgroundThreadFactory<LibraryBackendInterface>* factory);
void set_watcher_factory(BackgroundThreadFactory<LibraryWatcher>* factory);
void Init();
void StartThreads(); void StartThreads();
LibraryDirectoryModel* GetDirectoryModel() const { return dir_model_; } LibraryDirectoryModel* GetDirectoryModel() const { return dir_model_; }
boost::shared_ptr<LibraryBackend> GetBackend() const { return backend_->Worker(); } boost::shared_ptr<LibraryBackendInterface> GetBackend() const { return backend_->Worker(); }
// Get information about the library // Get information about the library
void GetChildSongs(LibraryItem* item, QList<QUrl>* urls, SongList* songs) const; void GetChildSongs(LibraryItem* item, QList<QUrl>* urls, SongList* songs) const;
@ -52,7 +59,7 @@ class Library : public SimpleTreeModel<LibraryItem> {
void ScanStarted(); void ScanStarted();
void ScanFinished(); void ScanFinished();
void BackendReady(boost::shared_ptr<LibraryBackend> backend); void BackendReady(boost::shared_ptr<LibraryBackendInterface> backend);
public slots: public slots:
void SetFilterAge(int age); void SetFilterAge(int age);
@ -96,7 +103,9 @@ class Library : public SimpleTreeModel<LibraryItem> {
private: private:
EngineBase* engine_; EngineBase* engine_;
BackgroundThread<LibraryBackend>* backend_; boost::scoped_ptr<BackgroundThreadFactory<LibraryBackendInterface> > backend_factory_;
boost::scoped_ptr<BackgroundThreadFactory<LibraryWatcher> > watcher_factory_;
BackgroundThread<LibraryBackendInterface>* backend_;
BackgroundThread<LibraryWatcher>* watcher_; BackgroundThread<LibraryWatcher>* watcher_;
LibraryDirectoryModel* dir_model_; LibraryDirectoryModel* dir_model_;

View File

@ -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) LibraryBackend::LibraryBackend(QObject* parent, const QString& database_name)
: QObject(parent), : LibraryBackendInterface(parent),
injected_database_name_(database_name) injected_database_name_(database_name)
{ {
QSettings s; QSettings s;

View File

@ -15,11 +15,11 @@
#include "gtest/gtest_prod.h" #include "gtest/gtest_prod.h"
class LibraryBackend : public QObject { class LibraryBackendInterface : public QObject {
Q_OBJECT Q_OBJECT
public: public:
LibraryBackend(QObject* parent = 0, const QString& database_name = QString()); LibraryBackendInterface(QObject* parent = 0);
struct Album { struct Album {
QString artist; QString artist;
@ -30,6 +30,63 @@ class LibraryBackend : public QObject {
}; };
typedef QList<Album> AlbumList; typedef QList<Album> 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; static const int kSchemaVersion;
// This actually refers to the location of the sqlite database // 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 UpdateManualAlbumArt(const QString& artist, const QString& album, const QString& art);
void ForceCompilation(const QString& artist, const QString& album, bool on); 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: private:
struct CompilationInfo { struct CompilationInfo {
CompilationInfo() : has_samplers(false), has_not_samplers(false) {} CompilationInfo() : has_samplers(false), has_not_samplers(false) {}

View File

@ -7,7 +7,7 @@ LibraryDirectoryModel::LibraryDirectoryModel(QObject* parent)
{ {
} }
void LibraryDirectoryModel::SetBackend(boost::shared_ptr<LibraryBackend> backend) { void LibraryDirectoryModel::SetBackend(boost::shared_ptr<LibraryBackendInterface> backend) {
backend_ = backend; backend_ = backend;
connect(backend_.get(), SIGNAL(DirectoriesDiscovered(DirectoryList)), SLOT(DirectoriesDiscovered(DirectoryList))); connect(backend_.get(), SIGNAL(DirectoriesDiscovered(DirectoryList)), SLOT(DirectoriesDiscovered(DirectoryList)));

View File

@ -8,7 +8,7 @@
#include "directory.h" #include "directory.h"
class LibraryBackend; class LibraryBackendInterface;
class LibraryDirectoryModel : public QStandardItemModel { class LibraryDirectoryModel : public QStandardItemModel {
Q_OBJECT Q_OBJECT
@ -16,7 +16,7 @@ class LibraryDirectoryModel : public QStandardItemModel {
public: public:
LibraryDirectoryModel(QObject* parent = 0); LibraryDirectoryModel(QObject* parent = 0);
void SetBackend(boost::shared_ptr<LibraryBackend> backend); void SetBackend(boost::shared_ptr<LibraryBackendInterface> backend);
bool IsBackendReady() const { return backend_; } bool IsBackendReady() const { return backend_; }
// To be called by GUIs // To be called by GUIs
@ -35,7 +35,7 @@ class LibraryDirectoryModel : public QStandardItemModel {
static const int kIdRole = Qt::UserRole + 1; static const int kIdRole = Qt::UserRole + 1;
QIcon dir_icon_; QIcon dir_icon_;
boost::shared_ptr<LibraryBackend> backend_; boost::shared_ptr<LibraryBackendInterface> backend_;
}; };
#endif // LIBRARYDIRECTORYMODEL_H #endif // LIBRARYDIRECTORYMODEL_H

View File

@ -14,7 +14,7 @@
class QFileSystemWatcher; class QFileSystemWatcher;
class QTimer; class QTimer;
class LibraryBackend; class LibraryBackendInterface;
class LibraryWatcher : public QObject { class LibraryWatcher : public QObject {
Q_OBJECT Q_OBJECT
@ -22,7 +22,7 @@ class LibraryWatcher : public QObject {
public: public:
LibraryWatcher(QObject* parent = 0); LibraryWatcher(QObject* parent = 0);
void SetBackend(boost::shared_ptr<LibraryBackend> backend) { backend_ = backend; } void SetBackend(boost::shared_ptr<LibraryBackendInterface> backend) { backend_ = backend; }
void SetEngine(EngineBase* engine) { engine_ = engine; } // TODO: shared_ptr void SetEngine(EngineBase* engine) { engine_ = engine; } // TODO: shared_ptr
signals: signals:
@ -51,7 +51,7 @@ class LibraryWatcher : public QObject {
private: private:
EngineBase* engine_; EngineBase* engine_;
boost::shared_ptr<LibraryBackend> backend_; boost::shared_ptr<LibraryBackendInterface> backend_;
QFileSystemWatcher* fs_watcher_; QFileSystemWatcher* fs_watcher_;
QTimer* rescan_timer_; QTimer* rescan_timer_;

View File

@ -179,8 +179,8 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
connect(library_, SIGNAL(TotalSongCountUpdated(int)), ui_.library_view, SLOT(TotalSongCountUpdated(int))); connect(library_, SIGNAL(TotalSongCountUpdated(int)), ui_.library_view, SLOT(TotalSongCountUpdated(int)));
connect(library_, SIGNAL(ScanStarted()), SLOT(LibraryScanStarted())); connect(library_, SIGNAL(ScanStarted()), SLOT(LibraryScanStarted()));
connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished())); connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished()));
connect(library_, SIGNAL(BackendReady(boost::shared_ptr<LibraryBackend>)), connect(library_, SIGNAL(BackendReady(boost::shared_ptr<LibraryBackendInterface>)),
cover_manager_, SLOT(SetBackend(boost::shared_ptr<LibraryBackend>))); cover_manager_, SLOT(SetBackend(boost::shared_ptr<LibraryBackendInterface>)));
// Age filters // Age filters
QActionGroup* filter_age_group = new QActionGroup(this); QActionGroup* filter_age_group = new QActionGroup(this);
@ -311,6 +311,7 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
show(); show();
} }
library_->Init();
library_->StartThreads(); library_->StartThreads();
} }

View File

@ -31,10 +31,12 @@ target_link_libraries(gmock gtest)
set(TESTUTILS-SOURCES set(TESTUTILS-SOURCES
test_utils.cpp test_utils.cpp
mock_networkaccessmanager.cpp mock_networkaccessmanager.cpp
mock_taglib.cpp) mock_taglib.cpp
)
set(TESTUTILS-MOC-HEADERS set(TESTUTILS-MOC-HEADERS
mock_networkaccessmanager.h) mock_networkaccessmanager.h
)
qt4_wrap_cpp(TESTUTILS-SOURCES-MOC ${TESTUTILS-MOC-HEADERS}) qt4_wrap_cpp(TESTUTILS-SOURCES-MOC ${TESTUTILS-MOC-HEADERS})
@ -45,6 +47,10 @@ add_custom_target(test
echo "Running tests" echo "Running tests"
WORKING_DIRECTORY ${CURRENT_BINARY_DIR} 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. # Given a file foo_test.cpp, creates a target foo_test and adds it to the test target.
macro(add_test_file test_source) 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) target_link_libraries(${TEST_NAME} gmock clementine_lib test_utils)
add_custom_command(TARGET test POST_BUILD add_custom_command(TARGET test POST_BUILD
COMMAND ./${TEST_NAME}) COMMAND ./${TEST_NAME})
add_dependencies(test ${TEST_NAME}) add_dependencies(build_tests ${TEST_NAME})
endmacro (add_test_file) endmacro (add_test_file)
@ -66,3 +72,4 @@ add_test_file(song_test.cpp)
add_test_file(librarybackend_test.cpp) add_test_file(librarybackend_test.cpp)
add_test_file(albumcoverfetcher_test.cpp) add_test_file(albumcoverfetcher_test.cpp)
add_test_file(xspfparser_test.cpp) add_test_file(xspfparser_test.cpp)
add_test_file(library_test.cpp)

46
tests/library_test.cpp Normal file
View File

@ -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 <boost/scoped_ptr.hpp>
#include <QtDebug>
#include <QThread>
#include <QSignalSpy>
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<LibraryBackendInterface, MockLibraryBackend>);
library_->set_watcher_factory(
new FakeBackgroundThreadFactory<LibraryWatcher, LibraryWatcher>);
library_->Init();
backend_ = static_cast<MockLibraryBackend*>(library_->GetBackend().get());
}
boost::scoped_ptr<Library> 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();
}

View File

@ -1,13 +1,16 @@
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include <QApplication>
#include "resources_env.h" #include "resources_env.h"
#include "metatypes_env.h" #include "metatypes_env.h"
int main(int argc, char** argv) { int main(int argc, char** argv) {
testing::InitGoogleMock(&argc, argv); testing::InitGoogleMock(&argc, argv);
testing::AddGlobalTestEnvironment(new ResourcesEnvironment);
testing::AddGlobalTestEnvironment(new MetatypesEnvironment); testing::AddGlobalTestEnvironment(new MetatypesEnvironment);
QApplication a(argc, argv);
testing::AddGlobalTestEnvironment(new ResourcesEnvironment);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} }

View File

@ -0,0 +1,28 @@
#ifndef MOCK_BACKGROUNDTHREAD_H
#define MOCK_BACKGROUNDTHREAD_H
#include "backgroundthread.h"
template <typename InterfaceType, typename DerivedType>
class FakeBackgroundThread : public BackgroundThread<InterfaceType> {
public:
FakeBackgroundThread(QObject* parent) : BackgroundThread<InterfaceType>(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<InterfaceType>::worker_.reset(new DerivedType);
}
void Start() {
emit BackgroundThreadBase::Initialised();
}
};
template <typename InterfaceType, typename DerivedType>
class FakeBackgroundThreadFactory : public BackgroundThreadFactory<InterfaceType> {
public:
BackgroundThread<InterfaceType>* GetThread(QObject* parent) {
return new FakeBackgroundThread<InterfaceType, DerivedType>(parent);
}
};
#endif

View File

@ -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