mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-28 10:09:24 +01:00
Make it possible for the LibraryBackend to be mocked, and add a simple test for Library.
This commit is contained in:
parent
777cc79b95
commit
16e1deaade
@ -60,6 +60,7 @@ set(CLEMENTINE-SOURCES
|
||||
xspfparser.cpp
|
||||
globalshortcuts/globalshortcuts.cpp
|
||||
fixlastfm.cpp
|
||||
backgroundthread.cpp
|
||||
)
|
||||
|
||||
# Header files that have Q_OBJECT in
|
||||
|
@ -22,7 +22,7 @@ const char* AlbumCoverManager::kSettingsGroup = "CoverManager";
|
||||
AlbumCoverManager::AlbumCoverManager(QNetworkAccessManager* network, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
constructed_(false),
|
||||
cover_loader_(new BackgroundThread<AlbumCoverLoader>(this)),
|
||||
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(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<int>() << 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<LibraryBackend> backend) {
|
||||
void AlbumCoverManager::SetBackend(boost::shared_ptr<LibraryBackendInterface> 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;
|
||||
|
@ -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<LibraryBackend> backend);
|
||||
void SetBackend(boost::shared_ptr<LibraryBackendInterface> backend);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *);
|
||||
@ -70,7 +70,7 @@ class AlbumCoverManager : public QDialog {
|
||||
bool constructed_;
|
||||
|
||||
Ui::CoverManager ui_;
|
||||
boost::shared_ptr<LibraryBackend> backend_;
|
||||
boost::shared_ptr<LibraryBackendInterface> backend_;
|
||||
|
||||
QAction* filter_all_;
|
||||
QAction* filter_with_covers_;
|
||||
|
21
src/backgroundthread.cpp
Normal file
21
src/backgroundthread.cpp
Normal 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
|
||||
}
|
@ -13,11 +13,29 @@
|
||||
# include <sys/resource.h>
|
||||
#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 {
|
||||
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 <typename T>
|
||||
// This is the templated class that stores and returns the worker object.
|
||||
template <typename InterfaceType>
|
||||
class BackgroundThread : public BackgroundThreadBase {
|
||||
public:
|
||||
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:
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
BackgroundThread<T>::~BackgroundThread() {
|
||||
template <typename InterfaceType>
|
||||
BackgroundThread<InterfaceType>::~BackgroundThread() {
|
||||
if (isRunning()) {
|
||||
quit();
|
||||
if (wait(10000))
|
||||
@ -79,39 +129,27 @@ BackgroundThread<T>::~BackgroundThread() {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void BackgroundThread<T>::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 <typename InterfaceType, typename DerivedType>
|
||||
BackgroundThreadImplementation<InterfaceType, DerivedType>::
|
||||
BackgroundThreadImplementation(QObject* parent)
|
||||
: BackgroundThread<InterfaceType>(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 <typename InterfaceType, typename DerivedType>
|
||||
void BackgroundThreadImplementation<InterfaceType, DerivedType>::run() {
|
||||
BackgroundThreadBase::SetIOPriority();
|
||||
|
||||
BackgroundThread<InterfaceType>::worker_.reset(new DerivedType);
|
||||
|
||||
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
|
||||
|
@ -13,31 +13,49 @@
|
||||
Library::Library(EngineBase* engine, QObject* parent)
|
||||
: SimpleTreeModel<LibraryItem>(new LibraryItem(this), parent),
|
||||
engine_(engine),
|
||||
backend_(new BackgroundThread<LibraryBackend>(this)),
|
||||
watcher_(new BackgroundThread<LibraryWatcher>(this)),
|
||||
backend_factory_(new BackgroundThreadFactoryImplementation<LibraryBackendInterface, LibraryBackend>),
|
||||
watcher_factory_(new BackgroundThreadFactoryImplementation<LibraryWatcher, LibraryWatcher>),
|
||||
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<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() {
|
||||
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;
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include "libraryitem.h"
|
||||
#include "simpletreemodel.h"
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
class LibraryDirectoryModel;
|
||||
|
||||
class Library : public SimpleTreeModel<LibraryItem> {
|
||||
@ -29,10 +31,15 @@ class Library : public SimpleTreeModel<LibraryItem> {
|
||||
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();
|
||||
|
||||
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
|
||||
void GetChildSongs(LibraryItem* item, QList<QUrl>* urls, SongList* songs) const;
|
||||
@ -52,7 +59,7 @@ class Library : public SimpleTreeModel<LibraryItem> {
|
||||
void ScanStarted();
|
||||
void ScanFinished();
|
||||
|
||||
void BackendReady(boost::shared_ptr<LibraryBackend> backend);
|
||||
void BackendReady(boost::shared_ptr<LibraryBackendInterface> backend);
|
||||
|
||||
public slots:
|
||||
void SetFilterAge(int age);
|
||||
@ -96,7 +103,9 @@ class Library : public SimpleTreeModel<LibraryItem> {
|
||||
|
||||
private:
|
||||
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_;
|
||||
LibraryDirectoryModel* dir_model_;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<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;
|
||||
|
||||
// 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) {}
|
||||
|
@ -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;
|
||||
|
||||
connect(backend_.get(), SIGNAL(DirectoriesDiscovered(DirectoryList)), SLOT(DirectoriesDiscovered(DirectoryList)));
|
||||
|
@ -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<LibraryBackend> backend);
|
||||
void SetBackend(boost::shared_ptr<LibraryBackendInterface> 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<LibraryBackend> backend_;
|
||||
boost::shared_ptr<LibraryBackendInterface> backend_;
|
||||
};
|
||||
|
||||
#endif // LIBRARYDIRECTORYMODEL_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<LibraryBackend> backend) { backend_ = backend; }
|
||||
void SetBackend(boost::shared_ptr<LibraryBackendInterface> 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<LibraryBackend> backend_;
|
||||
boost::shared_ptr<LibraryBackendInterface> backend_;
|
||||
|
||||
QFileSystemWatcher* fs_watcher_;
|
||||
QTimer* rescan_timer_;
|
||||
|
@ -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<LibraryBackend>)),
|
||||
cover_manager_, SLOT(SetBackend(boost::shared_ptr<LibraryBackend>)));
|
||||
connect(library_, SIGNAL(BackendReady(boost::shared_ptr<LibraryBackendInterface>)),
|
||||
cover_manager_, SLOT(SetBackend(boost::shared_ptr<LibraryBackendInterface>)));
|
||||
|
||||
// Age filters
|
||||
QActionGroup* filter_age_group = new QActionGroup(this);
|
||||
@ -311,6 +311,7 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
|
||||
show();
|
||||
}
|
||||
|
||||
library_->Init();
|
||||
library_->StartThreads();
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
46
tests/library_test.cpp
Normal file
46
tests/library_test.cpp
Normal 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();
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
28
tests/mock_backgroundthread.h
Normal file
28
tests/mock_backgroundthread.h
Normal 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
|
58
tests/mock_librarybackend.h
Normal file
58
tests/mock_librarybackend.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user