LibraryBackend tests should test at a higher level

This commit is contained in:
David Sansome 2010-03-02 23:37:12 +00:00
parent 4d3bd03f16
commit 3296bf7b7d
7 changed files with 132 additions and 150 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
SongList songs = backend_->GetSongs("foo", "bar");
ASSERT_EQ(1, songs.length());
EXPECT_EQ(42, songs[0].length());
EXPECT_EQ(42, songs[0].id());
} }
TEST_F(LibraryBackendTest, GetSongByIdSuccessfully) { TEST_F(LibraryBackendTest, AddSong) {
const char* query = // Add a directory
"SELECT ROWID, title, album, artist, albumartist, composer, " backend_->AddDirectory("/test");
"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; // Add the song
for (int i = 0; i < 24; ++i) { Song s = MakeDummySong(1);
// Fill every column with 42. s.set_title("Foo");
columns << 42; s.set_artist("Bar");
} s.set_album("Meep");
backend_->AddOrUpdateSongs(SongList() << s);
ExpectQuery(query, QList<QVariant>(), bind_values, columns); // Check the artist
QStringList artists = backend_->GetAllArtists();
ASSERT_EQ(1, artists.size());
EXPECT_EQ(s.artist(), artists[0]);
Song song = backend_->GetSongById(42); // Check the various album getters
EXPECT_EQ(42, song.length()); LibraryBackend::AlbumList albums = backend_->GetAllAlbums();
EXPECT_EQ(42, song.id()); 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, AddSongWithoutFilename) {
}
TEST_F(LibraryBackendTest, GetAlbumArtNonExistent) {
} }

11
tests/main.cpp Normal file
View 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
View 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