/* This file is part of Clementine. Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Clementine. If not, see . */ #include "test_utils.h" #include "gtest/gtest.h" #include "gmock/gmock.h" #include "librarybackend.h" #include "song.h" #include #include #include #include using ::testing::_; using ::testing::AtMost; using ::testing::Invoke; using ::testing::Return; void PrintTo(const ::QString& str, std::ostream& os) { os << str.toStdString(); } class LibraryBackendTest : public ::testing::Test { protected: virtual void SetUp() { backend_.reset(new LibraryBackend(NULL, ":memory:")); connection_name_ = "thread_" + QString::number( reinterpret_cast(QThread::currentThread())); database_ = QSqlDatabase::database(connection_name_); } void TearDown() { // Make sure Qt does not re-use the connection. database_ = QSqlDatabase(); QSqlDatabase::removeDatabase(connection_name_); } Song MakeDummySong(int directory_id) { // Returns a valid song with all the required fields set Song ret; ret.set_directory_id(directory_id); ret.set_filename("foo.mp3"); ret.set_mtime(0); ret.set_ctime(0); ret.set_filesize(0); return ret; } boost::scoped_ptr backend_; QString connection_name_; QSqlDatabase database_; }; #ifdef Q_OS_UNIX #include #include struct PerfTimer { PerfTimer(int iterations) : iterations_(iterations) { gettimeofday(&start_time_, NULL); } ~PerfTimer() { gettimeofday(&end_time_, NULL); timeval elapsed_time; timersub(&end_time_, &start_time_, &elapsed_time); int elapsed_us = elapsed_time.tv_usec + elapsed_time.tv_sec * 1000000; qDebug() << "Elapsed:" << elapsed_us << "us"; qDebug() << "Time per iteration:" << float(elapsed_us) / iterations_ << "us"; } timeval start_time_; timeval end_time_; int iterations_; }; TEST_F(LibraryBackendTest, LikePerformance) { const int iterations = 1000000; const char* needle = "foo"; const char* haystack = "foobarbaz foobarbaz"; qDebug() << "Simple query"; { PerfTimer perf(iterations); for (int i = 0; i < iterations; ++i) { backend_->Like(needle, haystack); } } } #endif 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(LibraryBackend::kSchemaVersion, q.value(0).toInt()); EXPECT_FALSE(q.next()); } TEST_F(LibraryBackendTest, EmptyDatabase) { // Check the database is empty to start with QStringList artists = backend_->GetAllArtists(); EXPECT_TRUE(artists.isEmpty()); LibraryBackend::AlbumList albums = backend_->GetAllAlbums(); EXPECT_TRUE(albums.isEmpty()); } TEST_F(LibraryBackendTest, AddDirectory) { QSignalSpy spy(backend_.get(), SIGNAL(DirectoriesDiscovered(DirectoryList))); backend_->AddDirectory("/test"); // Check the signal was emitted correctly ASSERT_EQ(1, spy.count()); DirectoryList list = spy[0][0].value(); ASSERT_EQ(1, list.size()); EXPECT_EQ("/test", list[0].path); EXPECT_EQ(1, list[0].id); } TEST_F(LibraryBackendTest, RemoveDirectory) { // Add a directory Directory dir; dir.id = 1; dir.path = "/test"; backend_->AddDirectory(dir.path); QSignalSpy spy(backend_.get(), SIGNAL(DirectoriesDeleted(DirectoryList))); // Remove the directory again backend_->RemoveDirectory(dir); // Check the signal was emitted correctly ASSERT_EQ(1, spy.count()); DirectoryList list = spy[0][0].value(); ASSERT_EQ(1, list.size()); EXPECT_EQ("/test", list[0].path); EXPECT_EQ(1, list[0].id); } TEST_F(LibraryBackendTest, AddInvalidSong) { // Adding a song without certain fields set should fail backend_->AddDirectory("/test"); Song s; s.set_directory_id(1); QSignalSpy spy(backend_.get(), SIGNAL(Error(QString))); backend_->AddOrUpdateSongs(SongList() << s); ASSERT_EQ(1, spy.count()); spy.takeFirst(); s.set_filename("foo"); backend_->AddOrUpdateSongs(SongList() << s); ASSERT_EQ(1, spy.count()); spy.takeFirst(); s.set_filesize(100); backend_->AddOrUpdateSongs(SongList() << s); ASSERT_EQ(1, spy.count()); spy.takeFirst(); s.set_mtime(100); backend_->AddOrUpdateSongs(SongList() << s); ASSERT_EQ(1, spy.count()); spy.takeFirst(); s.set_ctime(100); backend_->AddOrUpdateSongs(SongList() << s); ASSERT_EQ(0, spy.count()); } TEST_F(LibraryBackendTest, GetAlbumArtNonExistent) { } TEST_F(LibraryBackendTest, LikeWorksWithAllAscii) { EXPECT_TRUE(backend_->Like("%ar%", "bar")); EXPECT_FALSE(backend_->Like("%ar%", "foo")); } TEST_F(LibraryBackendTest, LikeWorksWithUnicode) { EXPECT_TRUE(backend_->Like("%Снег%", "Снег")); EXPECT_FALSE(backend_->Like("%Снег%", "foo")); } TEST_F(LibraryBackendTest, LikeAsciiCaseInsensitive) { EXPECT_TRUE(backend_->Like("%ar%", "BAR")); EXPECT_FALSE(backend_->Like("%ar%", "FOO")); } TEST_F(LibraryBackendTest, LikeUnicodeCaseInsensitive) { EXPECT_TRUE(backend_->Like("%снег%", "Снег")); } TEST_F(LibraryBackendTest, LikeCacheInvalidated) { EXPECT_TRUE(backend_->Like("%foo%", "foobar")); EXPECT_FALSE(backend_->Like("%baz%", "foobar")); } TEST_F(LibraryBackendTest, LikeQuerySplit) { EXPECT_TRUE(backend_->Like("%foo bar%", "foobar")); EXPECT_FALSE(backend_->Like("%foo bar%", "barbaz")); EXPECT_FALSE(backend_->Like("%foo bar%", "foobaz")); EXPECT_FALSE(backend_->Like("%foo bar%", "baz")); } // Test adding a single song to the database, then getting various information // back about it. class SingleSong : public LibraryBackendTest { protected: virtual void SetUp() { LibraryBackendTest::SetUp(); // Add a directory - this will get ID 1 backend_->AddDirectory("/test"); // Make a song in that directory song_ = MakeDummySong(1); song_.set_title("Title"); song_.set_artist("Artist"); song_.set_album("Album"); } void AddDummySong() { QSignalSpy added_spy(backend_.get(), SIGNAL(SongsDiscovered(SongList))); QSignalSpy deleted_spy(backend_.get(), SIGNAL(SongsDeleted(SongList))); // Add the song backend_->AddOrUpdateSongs(SongList() << song_); // Check the correct signals were emitted EXPECT_EQ(0, deleted_spy.count()); ASSERT_EQ(1, added_spy.count()); SongList list = added_spy[0][0].value(); ASSERT_EQ(1, list.count()); EXPECT_EQ(song_.title(), list[0].title()); EXPECT_EQ(song_.artist(), list[0].artist()); EXPECT_EQ(song_.album(), list[0].album()); EXPECT_EQ(1, list[0].id()); EXPECT_EQ(1, list[0].directory_id()); } Song song_; }; TEST_F(SingleSong, GetSongWithNoAlbum) { song_.set_album(""); AddDummySong(); if (HasFatalFailure()) return; EXPECT_EQ(1, backend_->GetAllArtists().size()); LibraryBackend::AlbumList albums = backend_->GetAllAlbums(); EXPECT_EQ(1, albums.size()); EXPECT_EQ("Artist", albums[0].artist); EXPECT_EQ("", albums[0].album_name); } TEST_F(SingleSong, GetAllArtists) { AddDummySong(); if (HasFatalFailure()) return; QStringList artists = backend_->GetAllArtists(); ASSERT_EQ(1, artists.size()); EXPECT_EQ(song_.artist(), artists[0]); } TEST_F(SingleSong, GetAllAlbums) { AddDummySong(); if (HasFatalFailure()) return; LibraryBackend::AlbumList albums = backend_->GetAllAlbums(); ASSERT_EQ(1, albums.size()); EXPECT_EQ(song_.album(), albums[0].album_name); EXPECT_EQ(song_.artist(), albums[0].artist); } TEST_F(SingleSong, GetAlbumsByArtist) { AddDummySong(); if (HasFatalFailure()) return; LibraryBackend::AlbumList albums = backend_->GetAlbumsByArtist("Artist"); ASSERT_EQ(1, albums.size()); EXPECT_EQ(song_.album(), albums[0].album_name); EXPECT_EQ(song_.artist(), albums[0].artist); } TEST_F(SingleSong, GetAlbumArt) { AddDummySong(); if (HasFatalFailure()) return; LibraryBackend::Album album = backend_->GetAlbumArt("Artist", "Album"); EXPECT_EQ(song_.album(), album.album_name); EXPECT_EQ(song_.artist(), album.artist); } TEST_F(SingleSong, GetSongs) { AddDummySong(); if (HasFatalFailure()) return; SongList songs = backend_->GetSongs("Artist", "Album"); ASSERT_EQ(1, songs.size()); EXPECT_EQ(song_.album(), songs[0].album()); EXPECT_EQ(song_.artist(), songs[0].artist()); EXPECT_EQ(song_.title(), songs[0].title()); EXPECT_EQ(1, songs[0].id()); } TEST_F(SingleSong, GetSongById) { AddDummySong(); if (HasFatalFailure()) return; Song song = backend_->GetSongById(1); EXPECT_EQ(song_.album(), song.album()); EXPECT_EQ(song_.artist(), song.artist()); EXPECT_EQ(song_.title(), song.title()); EXPECT_EQ(1, song.id()); } TEST_F(SingleSong, FindSongsInDirectory) { AddDummySong(); if (HasFatalFailure()) return; SongList songs = backend_->FindSongsInDirectory(1); ASSERT_EQ(1, songs.size()); EXPECT_EQ(song_.album(), songs[0].album()); EXPECT_EQ(song_.artist(), songs[0].artist()); EXPECT_EQ(song_.title(), songs[0].title()); EXPECT_EQ(1, songs[0].id()); } TEST_F(SingleSong, UpdateSong) { AddDummySong(); if (HasFatalFailure()) return; Song new_song(song_); new_song.set_id(1); new_song.set_title("A different title"); QSignalSpy deleted_spy(backend_.get(), SIGNAL(SongsDeleted(SongList))); QSignalSpy added_spy(backend_.get(), SIGNAL(SongsDiscovered(SongList))); backend_->AddOrUpdateSongs(SongList() << new_song); ASSERT_EQ(1, added_spy.size()); ASSERT_EQ(1, deleted_spy.size()); SongList songs_added = added_spy[0][0].value(); SongList songs_deleted = deleted_spy[0][0].value(); ASSERT_EQ(1, songs_added.size()); ASSERT_EQ(1, songs_deleted.size()); EXPECT_EQ("Title", songs_deleted[0].title()); EXPECT_EQ("A different title", songs_added[0].title()); EXPECT_EQ(1, songs_deleted[0].id()); EXPECT_EQ(1, songs_added[0].id()); } TEST_F(SingleSong, DeleteSongs) { AddDummySong(); if (HasFatalFailure()) return; Song new_song(song_); new_song.set_id(1); QSignalSpy deleted_spy(backend_.get(), SIGNAL(SongsDeleted(SongList))); backend_->DeleteSongs(SongList() << new_song); ASSERT_EQ(1, deleted_spy.size()); SongList songs_deleted = deleted_spy[0][0].value(); ASSERT_EQ(1, songs_deleted.size()); EXPECT_EQ("Title", songs_deleted[0].title()); EXPECT_EQ(1, songs_deleted[0].id()); // Check we can't retreive that song any more Song song = backend_->GetSongById(1); EXPECT_FALSE(song.is_valid()); EXPECT_EQ(-1, song.id()); // And the artist or album shouldn't show up either QStringList artists = backend_->GetAllArtists(); EXPECT_EQ(0, artists.size()); LibraryBackend::AlbumList albums = backend_->GetAllAlbums(); EXPECT_EQ(0, albums.size()); }