LibraryBackend tests should test at a higher level
This commit is contained in:
parent
4d3bd03f16
commit
3296bf7b7d
@ -14,18 +14,18 @@
|
|||||||
const char* LibraryBackend::kDatabaseName = "clementine.db";
|
const char* LibraryBackend::kDatabaseName = "clementine.db";
|
||||||
const int LibraryBackend::kSchemaVersion = 2;
|
const int LibraryBackend::kSchemaVersion = 2;
|
||||||
|
|
||||||
LibraryBackend::LibraryBackend(QObject* parent, QSqlDriver* driver)
|
LibraryBackend::LibraryBackend(QObject* parent, const QString& database_name)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
injected_driver_(driver)
|
injected_database_name_(database_name)
|
||||||
{
|
{
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup("Library");
|
s.beginGroup("Library");
|
||||||
directory_ = s.value("database_directory", DefaultDirectory()).toString();
|
directory_ = s.value("database_directory", DefaultDatabaseDirectory()).toString();
|
||||||
|
|
||||||
Connect();
|
Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LibraryBackend::DefaultDirectory() {
|
QString LibraryBackend::DefaultDatabaseDirectory() {
|
||||||
QDir ret(QDir::homePath() + "/.config/" + QCoreApplication::organizationName());
|
QDir ret(QDir::homePath() + "/.config/" + QCoreApplication::organizationName());
|
||||||
return QDir::toNativeSeparators(ret.path());
|
return QDir::toNativeSeparators(ret.path());
|
||||||
}
|
}
|
||||||
@ -49,12 +49,13 @@ QSqlDatabase LibraryBackend::Connect() {
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (injected_driver_) {
|
|
||||||
db = QSqlDatabase::addDatabase(injected_driver_, connection_id);
|
|
||||||
} else {
|
|
||||||
db = QSqlDatabase::addDatabase("QSQLITE", connection_id);
|
db = QSqlDatabase::addDatabase("QSQLITE", connection_id);
|
||||||
|
|
||||||
|
if (!injected_database_name_.isNull())
|
||||||
|
db.setDatabaseName(injected_database_name_);
|
||||||
|
else
|
||||||
db.setDatabaseName(directory_ + "/" + kDatabaseName);
|
db.setDatabaseName(directory_ + "/" + kDatabaseName);
|
||||||
}
|
|
||||||
if (!db.open()) {
|
if (!db.open()) {
|
||||||
emit Error("LibraryBackend: " + db.lastError().text());
|
emit Error("LibraryBackend: " + db.lastError().text());
|
||||||
return db;
|
return db;
|
||||||
|
@ -15,7 +15,7 @@ class LibraryBackend : public QObject {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LibraryBackend(QObject* parent = 0, QSqlDriver* driver = 0);
|
LibraryBackend(QObject* parent = 0, const QString& database_name = QString());
|
||||||
|
|
||||||
struct Album {
|
struct Album {
|
||||||
QString artist;
|
QString artist;
|
||||||
@ -27,7 +27,7 @@ class LibraryBackend : public QObject {
|
|||||||
typedef QList<Album> AlbumList;
|
typedef QList<Album> AlbumList;
|
||||||
|
|
||||||
// This actually refers to the location of the sqlite database
|
// This actually refers to the location of the sqlite database
|
||||||
static QString DefaultDirectory();
|
static QString DefaultDatabaseDirectory();
|
||||||
|
|
||||||
// Get a list of directories in the library. Emits DirectoriesDiscovered.
|
// Get a list of directories in the library. Emits DirectoriesDiscovered.
|
||||||
void LoadDirectoriesAsync();
|
void LoadDirectoriesAsync();
|
||||||
@ -107,7 +107,8 @@ class LibraryBackend : public QObject {
|
|||||||
QString directory_;
|
QString directory_;
|
||||||
QMutex connect_mutex_;
|
QMutex connect_mutex_;
|
||||||
|
|
||||||
QSqlDriver* injected_driver_;
|
// Used by tests
|
||||||
|
QString injected_database_name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LIBRARYBACKEND_H
|
#endif // LIBRARYBACKEND_H
|
||||||
|
@ -142,6 +142,10 @@ class Song {
|
|||||||
void set_art_manual(const QString& v) { d->art_manual_ = v; }
|
void set_art_manual(const QString& v) { d->art_manual_ = v; }
|
||||||
void set_image(const QImage& i) { d->image_ = i; }
|
void set_image(const QImage& i) { d->image_ = i; }
|
||||||
|
|
||||||
|
// Setters that should only be used by tests
|
||||||
|
void set_filename(const QString& v) { d->filename_ = v; }
|
||||||
|
void set_directory_id(int v) { d->directory_id_ = v; }
|
||||||
|
|
||||||
// Comparison functions
|
// Comparison functions
|
||||||
bool IsMetadataEqual(const Song& other) const;
|
bool IsMetadataEqual(const Song& other) const;
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ macro(add_test_file test_source)
|
|||||||
get_filename_component(TEST_NAME ${ARGV0} NAME_WE)
|
get_filename_component(TEST_NAME ${ARGV0} NAME_WE)
|
||||||
add_executable(${TEST_NAME}
|
add_executable(${TEST_NAME}
|
||||||
${ARGV0}
|
${ARGV0}
|
||||||
../3rdparty/gmock/src/gmock_main.cc)
|
main.cpp
|
||||||
|
)
|
||||||
target_link_libraries(${TEST_NAME} gmock clementine_lib)
|
target_link_libraries(${TEST_NAME} gmock clementine_lib)
|
||||||
add_custom_command(TARGET test POST_BUILD
|
add_custom_command(TARGET test POST_BUILD
|
||||||
COMMAND ./${TEST_NAME})
|
COMMAND ./${TEST_NAME})
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include "gmock/gmock-printers.h"
|
|
||||||
#include "test_utils.h"
|
#include "test_utils.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "mock_sqldriver.h"
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
#include "librarybackend.h"
|
#include "librarybackend.h"
|
||||||
|
#include "song.h"
|
||||||
|
|
||||||
#include <boost/scoped_ptr.hpp>
|
#include <boost/scoped_ptr.hpp>
|
||||||
|
|
||||||
@ -15,167 +15,115 @@ using ::testing::AtMost;
|
|||||||
using ::testing::Invoke;
|
using ::testing::Invoke;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
|
|
||||||
using boost::scoped_ptr;
|
|
||||||
|
|
||||||
void PrintTo(const ::QString& str, std::ostream& os) {
|
void PrintTo(const ::QString& str, std::ostream& os) {
|
||||||
os << str.toStdString();
|
os << str.toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
class LibraryBackendTest : public ::testing::Test {
|
class LibraryBackendTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
LibraryBackendTest()
|
|
||||||
: current_result_(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void SetUp() {
|
virtual void SetUp() {
|
||||||
// Owned by QSqlDatabase.
|
backend_.reset(new LibraryBackend(NULL, ":memory:"));
|
||||||
driver_ = new MockSqlDriver;
|
|
||||||
// DB connect calls.
|
|
||||||
EXPECT_CALL(*driver_, open(_, _, _, _, _, _)).WillOnce(Return(true));
|
|
||||||
EXPECT_CALL(*driver_, isOpen()).WillRepeatedly(Return(true));
|
|
||||||
EXPECT_CALL(*driver_, hasFeature(_)).WillRepeatedly(Return(true));
|
|
||||||
|
|
||||||
QStringList tables;
|
connection_name_ = "thread_" + QString::number(
|
||||||
tables << "Foo";
|
reinterpret_cast<quint64>(QThread::currentThread()));
|
||||||
EXPECT_CALL(*driver_, tables(QSql::Tables)).WillOnce(Return(tables));
|
database_ = QSqlDatabase::database(connection_name_);
|
||||||
|
|
||||||
MockSqlResult* result = new MockSqlResult(driver_);
|
|
||||||
EXPECT_CALL(*driver_, createResult()).
|
|
||||||
WillOnce(Return(result));
|
|
||||||
EXPECT_CALL(*result, reset(QString("SELECT version FROM schema_version"))).WillOnce(
|
|
||||||
DoAll(
|
|
||||||
Invoke(result, &MockSqlResult::hackSetActive),
|
|
||||||
Return(true)));
|
|
||||||
EXPECT_CALL(*result, fetch(1)).WillOnce(
|
|
||||||
DoAll(
|
|
||||||
Invoke(result, &MockSqlResult::setAt),
|
|
||||||
Return(true)));
|
|
||||||
EXPECT_CALL(*result, data(0)).WillOnce(Return(QVariant(2)));
|
|
||||||
|
|
||||||
EXPECT_CALL(*driver_, close());
|
|
||||||
|
|
||||||
backend_.reset(new LibraryBackend(NULL, driver_));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TearDown() {
|
void TearDown() {
|
||||||
// Make sure Qt does not re-use the connection.
|
// Make sure Qt does not re-use the connection.
|
||||||
QSqlDatabase::removeDatabase("thread_" + QString::number(
|
database_ = QSqlDatabase();
|
||||||
reinterpret_cast<quint64>(QThread::currentThread())));
|
QSqlDatabase::removeDatabase(connection_name_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this to mock a single query with one row of results.
|
Song MakeDummySong(int directory_id) {
|
||||||
void ExpectQuery(const QString& query,
|
// Returns a valid song with all the required fields set
|
||||||
const QList<QVariant>& bind_values,
|
Song ret;
|
||||||
const QMap<QString, QVariant>& named_bind_values,
|
ret.set_directory_id(directory_id);
|
||||||
const QList<QVariant>& columns) {
|
ret.set_filename("foo.mp3");
|
||||||
// Owned by QSqlDatabase.
|
ret.set_mtime(0);
|
||||||
current_result_ = new MockSqlResult(driver_);
|
ret.set_ctime(0);
|
||||||
EXPECT_CALL(*driver_, createResult()).
|
ret.set_filesize(0);
|
||||||
WillOnce(Return(current_result_));
|
return ret;
|
||||||
|
|
||||||
// Query string is set.
|
|
||||||
EXPECT_CALL(*current_result_, reset(QString(query))).WillOnce(
|
|
||||||
DoAll(
|
|
||||||
Invoke(current_result_, &MockSqlResult::hackSetActive),
|
|
||||||
Return(true)));
|
|
||||||
// Query is executed.
|
|
||||||
EXPECT_CALL(*current_result_, exec()).WillOnce(Return(true));
|
|
||||||
|
|
||||||
// Values are bound.
|
|
||||||
for (int i = 0; i < bind_values.size(); ++i) {
|
|
||||||
ExpectBind(i, bind_values[i]);
|
|
||||||
}
|
|
||||||
for (QMap<QString, QVariant>::const_iterator it = named_bind_values.begin();
|
|
||||||
it != named_bind_values.end(); ++it) {
|
|
||||||
ExpectBind(it.key(), it.value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// One row is fetched.
|
boost::scoped_ptr<LibraryBackend> backend_;
|
||||||
EXPECT_CALL(*current_result_, fetch(1)).WillOnce(
|
QString connection_name_;
|
||||||
DoAll(
|
QSqlDatabase database_;
|
||||||
Invoke(current_result_, &MockSqlResult::setAt),
|
|
||||||
Return(true)));
|
|
||||||
// Tries to fetch second row but we say we do not have any more rows.
|
|
||||||
// Can be called 0-1 times. This catches the case where a query is only expected
|
|
||||||
// to have one row so QSqlQuery::next() is only called once.
|
|
||||||
EXPECT_CALL(*current_result_, fetch(2)).Times(AtMost(1)).WillOnce(Return(false));
|
|
||||||
|
|
||||||
// Expect data() calls for each column.
|
|
||||||
ExpectData(columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExpectBind(int index, const QVariant& value) {
|
|
||||||
EXPECT_CALL(*current_result_, bindValue(index, value, _));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExpectBind(const QString& bind, const QVariant& value) {
|
|
||||||
EXPECT_CALL(*current_result_, bindValue(bind, value, _));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExpectData(const QList<QVariant>& columns) {
|
|
||||||
for (int i = 0; i < columns.size(); ++i) {
|
|
||||||
EXPECT_CALL(*current_result_, data(i)).WillRepeatedly(
|
|
||||||
Return(columns[i])).RetiresOnSaturation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MockSqlDriver* driver_;
|
|
||||||
scoped_ptr<LibraryBackend> backend_;
|
|
||||||
|
|
||||||
MockSqlResult* current_result_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(LibraryBackendTest, DatabaseInitialises) {
|
TEST_F(LibraryBackendTest, DatabaseInitialises) {
|
||||||
|
// Check that these tables exist
|
||||||
|
QStringList tables = database_.tables();
|
||||||
|
EXPECT_TRUE(tables.contains("songs"));
|
||||||
|
EXPECT_TRUE(tables.contains("directories"));
|
||||||
|
ASSERT_TRUE(tables.contains("schema_version"));
|
||||||
|
|
||||||
|
// Check the schema version is correct
|
||||||
|
QSqlQuery q("SELECT version FROM schema_version", database_);
|
||||||
|
ASSERT_TRUE(q.exec());
|
||||||
|
ASSERT_TRUE(q.next());
|
||||||
|
EXPECT_EQ(2, q.value(0).toInt());
|
||||||
|
EXPECT_FALSE(q.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LibraryBackendTest, GetSongsSuccessfully) {
|
TEST_F(LibraryBackendTest, EmptyDatabase) {
|
||||||
const char* query =
|
// Check the database is empty to start with
|
||||||
"SELECT ROWID, title, album, artist, albumartist, composer, "
|
QStringList artists = backend_->GetAllArtists();
|
||||||
"track, disc, bpm, year, genre, comment, compilation, "
|
EXPECT_TRUE(artists.isEmpty());
|
||||||
"length, bitrate, samplerate, directory, filename, "
|
|
||||||
"mtime, ctime, filesize, sampler, art_automatic, art_manual"
|
|
||||||
" FROM songs "
|
|
||||||
"WHERE (compilation = 0 AND sampler = 0) AND artist = ?"
|
|
||||||
" AND album = ?";
|
|
||||||
QList<QVariant> bind_values;
|
|
||||||
bind_values << "foo";
|
|
||||||
bind_values << "bar";
|
|
||||||
|
|
||||||
QList<QVariant> columns;
|
LibraryBackend::AlbumList albums = backend_->GetAllAlbums();
|
||||||
for (int i = 0; i < 24; ++i) {
|
EXPECT_TRUE(albums.isEmpty());
|
||||||
// Fill every column with 42.
|
|
||||||
columns << 42;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpectQuery(query, bind_values, QMap<QString, QVariant>(), columns);
|
TEST_F(LibraryBackendTest, AddSong) {
|
||||||
|
// Add a directory
|
||||||
|
backend_->AddDirectory("/test");
|
||||||
|
|
||||||
SongList songs = backend_->GetSongs("foo", "bar");
|
// Add the song
|
||||||
ASSERT_EQ(1, songs.length());
|
Song s = MakeDummySong(1);
|
||||||
EXPECT_EQ(42, songs[0].length());
|
s.set_title("Foo");
|
||||||
EXPECT_EQ(42, songs[0].id());
|
s.set_artist("Bar");
|
||||||
|
s.set_album("Meep");
|
||||||
|
backend_->AddOrUpdateSongs(SongList() << s);
|
||||||
|
|
||||||
|
// Check the artist
|
||||||
|
QStringList artists = backend_->GetAllArtists();
|
||||||
|
ASSERT_EQ(1, artists.size());
|
||||||
|
EXPECT_EQ(s.artist(), artists[0]);
|
||||||
|
|
||||||
|
// Check the various album getters
|
||||||
|
LibraryBackend::AlbumList albums = backend_->GetAllAlbums();
|
||||||
|
ASSERT_EQ(1, albums.size());
|
||||||
|
EXPECT_EQ(s.album(), albums[0].album_name);
|
||||||
|
EXPECT_EQ(s.artist(), albums[0].artist);
|
||||||
|
|
||||||
|
albums = backend_->GetAlbumsByArtist("Bar");
|
||||||
|
ASSERT_EQ(1, albums.size());
|
||||||
|
EXPECT_EQ(s.album(), albums[0].album_name);
|
||||||
|
EXPECT_EQ(s.artist(), albums[0].artist);
|
||||||
|
|
||||||
|
LibraryBackend::Album album = backend_->GetAlbumArt("Bar", "Meep");
|
||||||
|
EXPECT_EQ(s.album(), album.album_name);
|
||||||
|
EXPECT_EQ(s.artist(), album.artist);
|
||||||
|
|
||||||
|
// Check we can get the song back
|
||||||
|
SongList songs = backend_->GetSongs("Bar", "Meep");
|
||||||
|
ASSERT_EQ(1, songs.size());
|
||||||
|
EXPECT_EQ(s.album(), songs[0].album());
|
||||||
|
EXPECT_EQ(s.artist(), songs[0].artist());
|
||||||
|
EXPECT_EQ(s.title(), songs[0].title());
|
||||||
|
EXPECT_EQ(1, songs[0].id());
|
||||||
|
|
||||||
|
// Check we can get the song by ID
|
||||||
|
Song song2 = backend_->GetSongById(1);
|
||||||
|
EXPECT_EQ(s.album(), song2.album());
|
||||||
|
EXPECT_EQ(s.artist(), song2.artist()); // This is an error - song2.artist() should obviously be "Blur"
|
||||||
|
EXPECT_EQ(s.title(), song2.title());
|
||||||
|
EXPECT_EQ(1, song2.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LibraryBackendTest, GetSongByIdSuccessfully) {
|
TEST_F(LibraryBackendTest, AddSongWithoutFilename) {
|
||||||
const char* query =
|
|
||||||
"SELECT ROWID, title, album, artist, albumartist, composer, "
|
|
||||||
"track, disc, bpm, year, genre, comment, compilation, "
|
|
||||||
"length, bitrate, samplerate, directory, filename, "
|
|
||||||
"mtime, ctime, filesize, sampler, art_automatic, art_manual"
|
|
||||||
" FROM songs "
|
|
||||||
"WHERE ROWID = :id";
|
|
||||||
QMap<QString, QVariant> bind_values;
|
|
||||||
bind_values[":id"] = 42;
|
|
||||||
|
|
||||||
QList<QVariant> columns;
|
|
||||||
for (int i = 0; i < 24; ++i) {
|
|
||||||
// Fill every column with 42.
|
|
||||||
columns << 42;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpectQuery(query, QList<QVariant>(), bind_values, columns);
|
TEST_F(LibraryBackendTest, GetAlbumArtNonExistent) {
|
||||||
|
|
||||||
Song song = backend_->GetSongById(42);
|
|
||||||
EXPECT_EQ(42, song.length());
|
|
||||||
EXPECT_EQ(42, song.id());
|
|
||||||
}
|
}
|
||||||
|
11
tests/main.cpp
Normal file
11
tests/main.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include "resources_env.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
testing::InitGoogleMock(&argc, argv);
|
||||||
|
|
||||||
|
testing::AddGlobalTestEnvironment(new ResourcesEnvironment);
|
||||||
|
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
16
tests/resources_env.h
Normal file
16
tests/resources_env.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#ifndef RESOURCES_ENV_H
|
||||||
|
#define RESOURCES_ENV_H
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <QResource>
|
||||||
|
|
||||||
|
class ResourcesEnvironment : public ::testing::Environment {
|
||||||
|
public:
|
||||||
|
void SetUp() {
|
||||||
|
Q_INIT_RESOURCE(data);
|
||||||
|
Q_INIT_RESOURCE(translations);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RESOURCES_ENV_H
|
Loading…
x
Reference in New Issue
Block a user