diff --git a/.travis.yml b/.travis.yml index 19b0faa3..11c942e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ addons: - libtag1-dev - libpam0g-dev - libgraphicsmagick++1-dev + - libgtest-dev compiler: - clang - gcc diff --git a/CMakeLists.txt b/CMakeLists.txt index 23dbb83e..b5ecf689 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib) pkg_check_modules(Config++ REQUIRED IMPORTED_TARGET libconfig++) pkg_check_modules(GraphicsMagick++ IMPORTED_TARGET GraphicsMagick++) +find_package(GTest REQUIRED) # WT if (NOT Wt_FOUND) diff --git a/INSTALL.md b/INSTALL.md index d9c08fdc..3a15efb6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -44,7 +44,7 @@ __Notes__: * a C++17 compiler is needed * ffmpeg version 4 minimum is required ```sh -apt-get install g++ cmake libboost-program-options-dev libboost-system-dev libavutil-dev libavformat-dev libstb-dev libconfig++-dev ffmpeg libtag1-dev libpam0g-dev +apt-get install g++ cmake libboost-program-options-dev libboost-system-dev libavutil-dev libavformat-dev libstb-dev libconfig++-dev ffmpeg libtag1-dev libpam0g-dev libgtest-dev ``` __Notes__: * libpam0g-dev is optional (only for using PAM authentication) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2d02c4c7..06411e51 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -36,7 +36,8 @@ ARG BUILD_PACKAGES=" \ openssl-dev \ boost-dev \ libconfig-dev \ - taglib-dev" + taglib-dev \ + gtest-dev" RUN apk add --no-cache --update ${BUILD_PACKAGES} diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index b3ebcc6b..368de2ed 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -103,13 +103,13 @@ createQuery(Session& session, for (std::string_view keyword : keywords) { clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'"); - query.bind("%%" + escapeLikeKeyword(keyword) + "%%"); + query.bind("%" + escapeLikeKeyword(keyword) + "%"); } for (std::string_view keyword : keywords) { sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'"); - query.bind("%%" + escapeLikeKeyword(keyword) + "%%"); + query.bind("%" + escapeLikeKeyword(keyword) + "%"); } query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")"); diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index 4086099f..5b0a2d1c 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -46,7 +46,7 @@ createQuery(Session& session, query.join("track t ON t.release_id = r.id"); for (std::string_view keyword : keywords) - query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%"); + query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + escapeLikeKeyword(keyword) + "%"); if (!clusterIds.empty()) { diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index 15dc8056..64dcd4f2 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -48,7 +48,7 @@ createQuery(Session& session, auto query {session.getDboSession().query(queryStr)}; for (std::string_view keyword : keywords) - query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%"); + query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + escapeLikeKeyword(keyword) + "%"); if (!clusterIds.empty()) { diff --git a/src/libs/database/impl/Utils.cpp b/src/libs/database/impl/Utils.cpp index d1b1df82..39849db1 100644 --- a/src/libs/database/impl/Utils.cpp +++ b/src/libs/database/impl/Utils.cpp @@ -26,7 +26,7 @@ namespace Database std::string escapeLikeKeyword(std::string_view keyword) { - return StringUtils::escapeString(keyword, "*_", escapeChar); + return StringUtils::escapeString(keyword, "%_", escapeChar); } } // namespace Database diff --git a/src/test/database/Artist.cpp b/src/test/database/Artist.cpp new file mode 100644 index 00000000..4c16f5fc --- /dev/null +++ b/src/test/database/Artist.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include "Common.hpp" + +using namespace Database; + +TEST_F(DatabaseFixture, SingleArtist) +{ + ScopedArtist artist {session, "MyArtist"}; + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {Artist::getAll(session, Artist::SortMethod::ByName)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + artists = Artist::getAllOrphans(session); + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleArtist) +{ + ScopedTrack track {session, "MyTrack"}; + ScopedArtist artist {session, "MyArtist"}; + + { + auto transaction {session.createUniqueTransaction()}; + + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {track->getArtists({TrackArtistLinkType::Artist})}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + EXPECT_EQ(artist->getReleaseCount(), 0); + + ASSERT_EQ(track->getArtistLinks().size(), 1); + auto artistLink {track->getArtistLinks().front()}; + EXPECT_EQ(artistLink->getTrack().id(), track.getId()); + EXPECT_EQ(artistLink->getArtist().id(), artist.getId()); + + ASSERT_EQ(track->getArtists({TrackArtistLinkType::Artist}).size(), 1); + EXPECT_TRUE(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty()); + EXPECT_EQ(track->getArtists({}).size(), 1); + } + + { + auto transaction {session.createUniqueTransaction()}; + + auto tracks {artist->getTracks()}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track.getId()); + + EXPECT_TRUE(artist->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); + EXPECT_EQ(artist->getTracks(TrackArtistLinkType::Artist).size(), 1); + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRoles) +{ + ScopedTrack track {session, "MyTrack"}; + ScopedArtist artist {session, "MyArtist"}; + { + auto transaction {session.createUniqueTransaction()}; + + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + bool hasMore{}; + EXPECT_EQ(Artist::getByFilter(session, {}, {}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, hasMore).size(), 1); + EXPECT_EQ(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::Artist, Artist::SortMethod::ByName, std::nullopt, hasMore).size(), 1); + EXPECT_EQ(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::ReleaseArtist, Artist::SortMethod::ByName, std::nullopt, hasMore).size(), 1); + EXPECT_EQ(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::Writer, Artist::SortMethod::ByName, std::nullopt, hasMore).size(), 1); + EXPECT_TRUE(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::Composer, Artist::SortMethod::ByName, std::nullopt, hasMore).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {track->getArtists({TrackArtistLinkType::Artist})}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + artists = track->getArtists({TrackArtistLinkType::ReleaseArtist}); + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + EXPECT_EQ(track->getArtistLinks().size(), 3); + + EXPECT_EQ(artist->getTracks().size(), 1); + EXPECT_EQ(artist->getTracks({TrackArtistLinkType::ReleaseArtist}).size(), 1); + EXPECT_EQ(artist->getTracks({TrackArtistLinkType::Artist}).size(), 1); + EXPECT_EQ(artist->getTracks({TrackArtistLinkType::Writer}).size(), 1); + } +} + +TEST_F(DatabaseFixture,SingleTrackMultiArtists) +{ + ScopedTrack track {session, "track"}; + ScopedArtist artist1 {session, "artist1"}; + ScopedArtist artist2 {session, "artist2"}; + ASSERT_NE(artist1.getId(), artist2.getId()); + + { + auto transaction {session.createUniqueTransaction()}; + + TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {track->getArtists({TrackArtistLinkType::Artist})}; + ASSERT_EQ(artists.size(), 2); + EXPECT_TRUE((artists[0].id() == artist1.getId() && artists[1].id() == artist2.getId()) + || (artists[0].id() == artist2.getId() && artists[1].id() == artist1.getId())); + + EXPECT_EQ(track->getArtists({}).size(), 2); + EXPECT_EQ(track->getArtists({TrackArtistLinkType::Artist}).size(), 2); + EXPECT_TRUE(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty()); + EXPECT_EQ(Artist::getAll(session, Artist::SortMethod::ByName).size(), 2); + EXPECT_EQ(Artist::getAllIds(session).size(), 2); + } + + { + auto transaction {session.createUniqueTransaction()}; + + EXPECT_EQ(artist1->getTracks().front(), track.get()); + EXPECT_EQ(artist2->getTracks().front(), track.get()); + + EXPECT_TRUE(artist1->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); + EXPECT_EQ(artist1->getTracks(TrackArtistLinkType::Artist).size(), 1); + EXPECT_TRUE(artist2->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); + EXPECT_EQ(artist2->getTracks(TrackArtistLinkType::Artist).size(), 1); + } +} + +TEST_F(DatabaseFixture, SingleArtistSearchByName) +{ + ScopedArtist artist {session, "AAA"}; + ScopedTrack track {session, "MyTrack"}; // filters does not work on orphans + + { + auto transaction {session.createUniqueTransaction()}; + artist.get().modify()->setSortName("ZZZ"); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool more {}; + EXPECT_TRUE(Artist::getByFilter(session, {}, {"N"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more).empty()); + + const auto artistsByAAA {Artist::Artist::getByFilter(session, {}, {"A"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; + ASSERT_EQ(artistsByAAA.size(), 1); + EXPECT_EQ(artistsByAAA.front().id(), artist.getId()); + + const auto artistsByZZZ {Artist::Artist::getByFilter(session, {}, {"Z"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; + ASSERT_EQ(artistsByZZZ.size(), 1); + EXPECT_EQ(artistsByZZZ.front().id(), artist.getId()); + + EXPECT_TRUE(Artist::getByName(session, "NNN").empty()); + } +} + +TEST_F(DatabaseFixture, MultipleArtistsSearchByNameEscaped) +{ + ScopedArtist artist1 {session, "MyArtist%"}; + ScopedArtist artist2 {session, "%MyArtist"}; + ScopedArtist artist3 {session, "%_MyArtist"}; + + ScopedArtist artist4 {session, "MyArtist%foo"}; + ScopedArtist artist5 {session, "foo%MyArtist"}; + ScopedArtist artist6 {session, "%AMyArtist"}; + + { + auto transaction {session.createSharedTransaction()}; + { + const auto artists {Artist::getByName(session, "MyArtist%")}; + ASSERT_TRUE(artists.size() == 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); + EXPECT_TRUE(Artist::getByName(session, "MyArtistFoo").empty()); + } + { + const auto artists {Artist::getByName(session, "%MyArtist")}; + ASSERT_TRUE(artists.size() == 1); + EXPECT_EQ(artists.front().id(), artist2.getId()); + EXPECT_TRUE(Artist::getByName(session, "FooMyArtist").empty()); + } + { + const auto artists {Artist::getByName(session, "%_MyArtist")}; + ASSERT_TRUE(artists.size() == 1); + ASSERT_EQ(artists.front().id(), artist3.getId()); + EXPECT_TRUE(Artist::getByName(session, "%CMyArtist").empty()); + } + } + + // get by filter only works with tracks links... + ScopedTrack track {session, "MyTrack"}; // filters does not work on orphans + + { + auto transaction {session.createUniqueTransaction()}; + + TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist3.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist4.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist5.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist6.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction {session.createSharedTransaction()}; + bool more; + { + const auto artists {Artist::getByFilter(session, {}, {"MyArtist"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; + EXPECT_EQ(artists.size(), 6); + } + + { + const auto artists {Artist::getByFilter(session, {}, {"MyArtist%"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist1.getId()); + EXPECT_EQ(artists[1].id(), artist4.getId()); + } + + { + const auto artists {Artist::getByFilter(session, {}, {"%MyArtist"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist2.getId()); + EXPECT_EQ(artists[1].id(), artist5.getId()); + } + + { + const auto artists {Artist::getByFilter(session, {}, {"_MyArtist"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists[0].id(), artist3.getId()); + } + } +} + +TEST_F(DatabaseFixture, MultiArtistsSortMethod) +{ + ScopedArtist artistA {session, "artistA"}; + ScopedArtist artistB {session, "artistB"}; + + { + auto transaction {session.createUniqueTransaction()}; + + artistA.get().modify()->setSortName("sortNameB"); + artistB.get().modify()->setSortName("sortNameA"); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto allArtistsByName {Artist::getAll(session, Artist::SortMethod::ByName)}; + auto allArtistsBySortName {Artist::getAll(session, Artist::SortMethod::BySortName)}; + + ASSERT_EQ(allArtistsByName.size(), 2); + EXPECT_EQ(allArtistsByName.front().id(), artistA.getId()); + EXPECT_EQ(allArtistsByName.back().id(), artistB.getId()); + + ASSERT_EQ(allArtistsBySortName.size(), 2); + EXPECT_EQ(allArtistsBySortName.front().id(), artistB.getId()); + EXPECT_EQ(allArtistsBySortName.back().id(), artistA.getId()); + } +} + diff --git a/src/test/database/CMakeLists.txt b/src/test/database/CMakeLists.txt index ac42aa6b..30c59057 100644 --- a/src/test/database/CMakeLists.txt +++ b/src/test/database/CMakeLists.txt @@ -1,11 +1,15 @@ add_executable(test-database + Artist.cpp + Cluster.cpp DatabaseTest.cpp + Track.cpp ) target_link_libraries(test-database PRIVATE lmsdatabase + GTest::GTest ) -add_test(NAME database COMMAND test-database) +gtest_discover_tests(test-database) diff --git a/src/test/database/Cluster.cpp b/src/test/database/Cluster.cpp new file mode 100644 index 00000000..9135b48c --- /dev/null +++ b/src/test/database/Cluster.cpp @@ -0,0 +1,1096 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include "Common.hpp" + +#include +#include + +using namespace Database; + +TEST_F(DatabaseFixture, SingleCluster) +{ + ScopedClusterType clusterType {session, "MyType"}; + + { + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + + { + auto transaction {session.createUniqueTransaction()}; + + auto clusters {Cluster::getAll(session)}; + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front().id(), cluster.getId()); + EXPECT_EQ(clusters.front()->getType().id(), clusterType.getId()); + + clusters = Cluster::getAllOrphans(session); + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front().id(), cluster.getId()); + + auto clusterTypes {ClusterType::getAll(session)}; + ASSERT_EQ(clusterTypes.size(), 1); + EXPECT_EQ(clusterTypes.front().id(), clusterType.getId()); + + clusterTypes = ClusterType::getAllUsed(session); + ASSERT_EQ(clusterTypes.size(), 1); + EXPECT_EQ(clusterTypes.front().id(), clusterType.getId()); + + clusterTypes = ClusterType::getAllOrphans(session); + EXPECT_TRUE(clusterTypes.empty()); + } + } + + { + auto transaction {session.createUniqueTransaction()}; + + auto clusterTypes {ClusterType::getAllOrphans(session)}; + ASSERT_EQ(clusterTypes.size(), 1); + EXPECT_EQ(clusterTypes.front().id(), clusterType.getId()); + + ASSERT_TRUE(ClusterType::getAllUsed(session).empty()); + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleCluster) +{ + ScopedTrack track {session, "MyTrack"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Cluster::getAllOrphans(session).empty()); + auto clusterTypes {ClusterType::getAllOrphans(session)}; + ASSERT_EQ(clusterTypes.size(), 1); + EXPECT_EQ(clusterTypes.front().id(), clusterType.getId()); + } + + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + { + auto transaction {session.createSharedTransaction()}; + auto clusters {Cluster::getAllOrphans(session)}; + EXPECT_EQ(clusters.size(), 2); + EXPECT_TRUE(track->getClusters().empty()); + EXPECT_TRUE(track->getClusterIds().empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Track::getAllIdsWithClusters(session).empty()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + cluster1.get().modify()->addTrack(track.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + auto tracks {Track::getAllIdsWithClusters(session)}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front(), track.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + auto clusters {Cluster::getAllOrphans(session)}; + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front().id(), cluster2.getId()); + + EXPECT_TRUE(ClusterType::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto tracks {Track::getByClusters(session, {cluster1.getId()})}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track.getId()); + + tracks = Track::getByClusters(session, {cluster2.getId()}); + EXPECT_TRUE(tracks.empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto clusters {track->getClusters()}; + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front().id(), cluster1.getId()); + + auto clusterIds {track->getClusterIds()}; + ASSERT_EQ(clusterIds.size(), 1); + EXPECT_EQ(clusterIds.front(), cluster1.getId()); + } +} + +TEST_F(DatabaseFixture, MultipleTracksSingleCluster) +{ + std::list tracks; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster.get().modify()->addTrack(tracks.back().get()); + } + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Cluster::getAllOrphans(session).empty()); + + EXPECT_EQ(cluster->getTracksCount(), tracks.size()); + + for (auto trackCluster : cluster->getTracks()) + { + auto it {std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackCluster.id() == track.getId(); })}; + EXPECT_TRUE(it != std::cend(tracks)); + } + } +} + +TEST_F(DatabaseFixture, MultipleTracksMultipleClustersTopRelease) +{ + ScopedClusterType clusterType {session, "ClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "Cluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "Cluster2"}; + ScopedCluster cluster3 {session, clusterType.lockAndGet(), "Cluster3"}; + ScopedTrack trackA {session, "TrackA"}; + ScopedTrack trackB {session, "TrackB"}; + ScopedTrack trackC {session, "TrackC"}; + ScopedRelease releaseA {session, "ReleaseA"}; + ScopedRelease releaseB {session, "ReleaseB"}; + ScopedRelease releaseC {session, "ReleaseC"}; + + ScopedUser user {session, "MyUser"}; + ScopedTrackList trackList {session, "TrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_EQ(trackList->getDuration(), std::chrono::seconds {0}); + } + + { + auto transaction {session.createUniqueTransaction()}; + + cluster1.get().modify()->addTrack(trackA.get()); + cluster2.get().modify()->addTrack(trackB.get()); + cluster2.get().modify()->addTrack(trackC.get()); + cluster3.get().modify()->addTrack(trackC.get()); + + trackA.get().modify()->setRelease(releaseA.get()); + trackB.get().modify()->setRelease(releaseB.get()); + trackC.get().modify()->setRelease(releaseC.get()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + TrackListEntry::create(session, trackA.get(), trackList.get()); + TrackListEntry::create(session, trackB.get(), trackList.get()); + TrackListEntry::create(session, trackB.get(), trackList.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool hasMore; + const auto releases{trackList->getTopReleases({}, std::nullopt, hasMore)}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), releaseB.getId()); + EXPECT_EQ(releases[1].id(), releaseA.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool hasMore; + auto releases{trackList->getTopReleases({cluster1.getId()}, std::nullopt, hasMore)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases[0].id(), releaseA.getId()); + + releases = trackList->getTopReleases({cluster2.getId()}, std::nullopt, hasMore); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases[0].id(), releaseB.getId()); + + releases = trackList->getTopReleases({cluster2.getId(), cluster1.getId()}, std::nullopt, hasMore); + EXPECT_TRUE(releases.empty()); + + releases = trackList->getTopReleases({cluster2.getId(), cluster3.getId()}, std::nullopt, hasMore); + EXPECT_TRUE(releases.empty()); + } + + + { + auto transaction {session.createUniqueTransaction()}; + + TrackListEntry::create(session, trackC.get(), trackList.get()); + TrackListEntry::create(session, trackC.get(), trackList.get()); + TrackListEntry::create(session, trackC.get(), trackList.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool hasMore; + auto releases {trackList->getTopReleases({cluster2.getId(), cluster3.getId()}, std::nullopt, hasMore)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases[0].id(), releaseC.getId()); + + releases = trackList->getTopReleases({cluster2.getId()}, std::nullopt, hasMore); + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), releaseC.getId()); + EXPECT_EQ(releases[1].id(), releaseB.getId()); + } +} + +TEST_F(DatabaseFixture,SingleTrackSingleReleaseSingleCluster) +{ + ScopedTrack track {session, "MyTrackFile"}; + ScopedRelease release {session, "MyRelease"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType .lockAndGet(), "MyCluster"}; + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Release::getAllIdsWithClusters(session).empty()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + track.get().modify()->setRelease(release.get()); + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + auto releases {Release::getAllIdsWithClusters(session)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front(), release.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_TRUE(Cluster::getAllOrphans(session).empty()); + EXPECT_TRUE(Release::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto releases {Release::getByClusters(session, {cluster.getId()})}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_EQ(cluster->getReleasesCount(), 1); + EXPECT_EQ(cluster->getTracksCount(), 1); + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters) +{ + ScopedTrack track {session, "MyTrackFile"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "Cluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "Cluster2"}; + ScopedCluster cluster3 {session, clusterType.lockAndGet(), "Cluster3"}; + { + auto transaction {session.createUniqueTransaction()}; + + auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist)}; + cluster1.get().modify()->addTrack(track.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(ClusterType::getAllOrphans(session).empty()); + EXPECT_EQ(Cluster::getAllOrphans(session).size(), 2); + EXPECT_TRUE(Release::getAllOrphans(session).empty()); + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_EQ(track->getClusters().size(), 1); + EXPECT_EQ(track->getClusterIds().size(), 1); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::SortMethod::ByName)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + EXPECT_TRUE(Artist::getByClusters(session, {cluster2.getId()}, Artist::SortMethod::ByName).empty()); + EXPECT_TRUE(Artist::getByClusters(session, {cluster3.getId()}, Artist::SortMethod::ByName).empty()); + + cluster2.get().modify()->addTrack(track.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::SortMethod::ByName)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + artists = Artist::getByClusters(session, {cluster2.getId()}, Artist::SortMethod::ByName); + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}, Artist::SortMethod::ByName); + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + EXPECT_TRUE(Artist::getByClusters(session, {cluster3.getId()}, Artist::SortMethod::ByName).empty()); + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters) +{ + ScopedTrack track {session, "MyTrackFile"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + + { + auto transaction {session.createUniqueTransaction()}; + + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Cluster::getAllOrphans(session).empty()); + EXPECT_TRUE(Release::getAllOrphans(session).empty()); + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::SortMethod::ByName)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + } +} + +TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters) +{ + constexpr std::size_t nbTracks {10}; + constexpr std::size_t nbClusters {5}; + + std::list tracks; + std::list clusters; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + + for (std::size_t i {}; i < nbClusters; ++i) + clusters.emplace_back(session, clusterType.lockAndGet(), "MyCluster" + std::to_string(i)); + + for (std::size_t i {}; i < nbTracks ; ++i) + { + tracks.emplace_back(session, "MyTrackFile" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist); + + for (auto& cluster : clusters) + cluster.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Cluster::getAllOrphans(session).empty()); + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + std::set clusterIds; + std::transform(std::cbegin(clusters), std::cend(clusters), std::inserter(clusterIds, std::begin(clusterIds)), [](const ScopedCluster& cluster) { return cluster.getId(); }); + + auto artists {Artist::getByClusters(session, clusterIds, Artist::SortMethod::ByName)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + } +} + + +TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity) +{ + std::list tracks; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyClusterType"}; + + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster.get().modify()->addTrack(tracks.back().get()); + } + } + + { + auto transaction {session.createSharedTransaction()}; + + const auto similarTracks {Track::getSimilarTracks(session, {tracks.front().getId()})}; + EXPECT_EQ(similarTracks.size(), tracks.size() - 1); + for (auto similarTrack : similarTracks) + { + EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), + [&](const ScopedTrack& track) + { + return similarTrack.id() == track.getId(); + }) != std::cend(tracks)); + } + } +} + +TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity) +{ + std::list tracks; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + for (std::size_t i {}; i < 5; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster1.get().modify()->addTrack(tracks.back().get()); + } + } + + for (std::size_t i {5}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster1.get().modify()->addTrack(tracks.back().get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + } + + { + auto transaction {session.createSharedTransaction()}; + + { + auto similarTracks {Track::getSimilarTracks(session, {tracks.back().getId()}, 0, 4)}; + EXPECT_EQ(similarTracks.size(), 4); + for (auto similarTrack : similarTracks) + EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const ScopedTrack& track) { return similarTrack.id() == track.getId(); }) != std::cend(tracks)); + } + + { + auto similarTracks {Track::getSimilarTracks(session, {tracks.front().getId()})}; + EXPECT_EQ(similarTracks.size(), tracks.size() - 1); + for (auto similarTrack : similarTracks) + EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const ScopedTrack& track) { return similarTrack.id() == track.getId(); }) != std::cend(tracks)); + } + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster) +{ + ScopedTrack track {session, "MyTrack"}; + ScopedRelease release {session, "MyRelease"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(Artist::getAllIdsWithClusters(session).empty()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + track.get().modify()->setRelease(release.get()); + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_TRUE(Cluster::getAllOrphans(session).empty()); + EXPECT_TRUE(ClusterType::getAllOrphans(session).empty()); + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); + EXPECT_TRUE(Release::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + auto artists {Artist::getAllIdsWithClusters(session)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front(), artist.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::SortMethod::ByName)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + + auto releases {artist->getReleases()}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); + + releases = artist->getReleases({cluster.getId()}); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); + } +} + +TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters) +{ + ScopedTrack track {session, "MyTrack"}; + ScopedRelease release {session, "MyRelease"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + { + auto transaction {session.createUniqueTransaction()}; + + auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist)}; + track.get().modify()->setRelease(release.get()); + cluster1.get().modify()->addTrack(track.get()); + cluster2.get().modify()->addTrack(track.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto releases {artist->getReleases()}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); + + releases = artist->getReleases({cluster1.getId(), cluster2.getId()}); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); + } +} + +TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster) +{ + ScopedUser user {session, "MyUser"}; + ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + std::list tracks; + + for (std::size_t i {}; i < 20; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + + if (i < 5) + TrackListEntry::create(session, tracks.back().get(), trackList.get()); + + if (i < 10) + cluster.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + const auto similarTracks {trackList->getSimilarTracks()}; + EXPECT_EQ(similarTracks.size(), 5); + + for (auto similarTrack : similarTracks) + EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 5), std::cend(tracks), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); + } +} + +TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters) +{ + ScopedUser user {session, "MyUser"}; + ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + std::list tracks; + + for (std::size_t i {}; i < 20; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + + if (i < 5) + TrackListEntry::create(session, tracks.back().get(), trackList.get()); + + if (i < 10) + { + cluster1.get().modify()->addTrack(tracks.back().get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + else if (i < 15) + { + cluster1.get().modify()->addTrack(tracks.back().get()); + } + } + + { + auto transaction {session.createSharedTransaction()}; + + { + const auto similarTracks {trackList->getSimilarTracks(0, 5)}; + ASSERT_EQ(similarTracks.size(), 5); + + for (auto similarTrack : similarTracks) + EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 5), std::next(std::cbegin(tracks), 10), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); + } + + { + const auto similarTracks {trackList->getSimilarTracks(5, 10)}; + ASSERT_EQ(similarTracks.size(), 5); + + for (auto similarTrack : similarTracks) + EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 10), std::next(std::cbegin(tracks), 15), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); + } + + EXPECT_TRUE(trackList->getSimilarTracks(10, 10).empty()); + } +} + +TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClustersRecentlyPlayed) +{ + ScopedUser user {session, "MyUser"}; + ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + ScopedCluster cluster3 {session, clusterType.lockAndGet(), "MyCluster3"}; + ScopedTrack track1 {session, "MyTrack1"}; + ScopedTrack track2 {session, "MyTrack1"}; + ScopedArtist artist1 {session, "MyArtist1"}; + ScopedArtist artist2 {session, "MyArtist2"}; + ScopedRelease release1 {session, "MyRelease1"}; + ScopedRelease release2 {session, "MyRelease2"}; + + const Wt::WDateTime now {Wt::WDateTime::currentDateTime()}; + + { + auto transaction {session.createUniqueTransaction()}; + + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); + + cluster1.get().modify()->addTrack(track1.get()); + cluster2.get().modify()->addTrack(track2.get()); + cluster3.get().modify()->addTrack(track1.get()); + cluster3.get().modify()->addTrack(track2.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + EXPECT_TRUE(trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults).empty()); + EXPECT_TRUE(trackList->getReleasesReverse({}, std::nullopt, moreResults).empty()); + EXPECT_TRUE(trackList->getTracksReverse({}, std::nullopt, moreResults).empty()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + TrackListEntry::create(session, track1.get(), trackList.get(), now); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); + + const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size() , 1); + EXPECT_EQ(releases.front().id(), release1.getId()); + + const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({cluster1.getId()}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); + + const auto releases {trackList->getReleasesReverse({cluster1.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release1.getId()); + + const auto tracks {trackList->getTracksReverse({cluster1.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({cluster3.getId()}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); + + const auto releases {trackList->getReleasesReverse({cluster3.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release1.getId()); + + const auto tracks {trackList->getTracksReverse({cluster3.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({cluster1.getId()}, TrackArtistLinkType::Artist, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({}, TrackArtistLinkType::Artist, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + EXPECT_TRUE(trackList->getArtistsReverse({cluster2.getId()}, std::nullopt, std::nullopt, moreResults).empty()); + EXPECT_TRUE(trackList->getReleasesReverse({cluster2.getId()}, std::nullopt, moreResults).empty()); + EXPECT_TRUE(trackList->getTracksReverse({cluster2.getId()}, std::nullopt, moreResults).empty()); + + EXPECT_TRUE(trackList->getArtistsReverse({}, TrackArtistLinkType::ReleaseArtist, std::nullopt, moreResults).empty()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + TrackListEntry::create(session, track2.get(), trackList.get(), now.addSecs(1)); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist2.getId()); + EXPECT_EQ(artists[1].id(), artist1.getId()); + + const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release2.getId()); + EXPECT_EQ(releases[1].id(), release1.getId()); + + const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track2.getId()); + EXPECT_EQ(tracks[1].id(),track1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({cluster3.getId()}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist2.getId()); + EXPECT_EQ(artists[1].id(), artist1.getId()); + + const auto releases {trackList->getReleasesReverse({cluster3.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release2.getId()); + EXPECT_EQ(releases[1].id(), release1.getId()); + + const auto tracks {trackList->getTracksReverse({cluster3.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track2.getId()); + EXPECT_EQ(tracks[1].id(), track1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({cluster1.getId()}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); + + const auto releases {trackList->getReleasesReverse({cluster1.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release1.getId()); + + const auto tracks {trackList->getTracksReverse({cluster1.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track1.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({cluster2.getId()}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist2.getId()); + + const auto releases {trackList->getReleasesReverse({cluster2.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release2.getId()); + + const auto tracks {trackList->getTracksReverse({cluster2.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track2.getId()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + TrackListEntry::create(session, track1.get(), trackList.get(), now.addSecs(2)); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist1.getId()); + EXPECT_EQ(artists[1].id(), artist2.getId()); + + const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release1.getId()); + EXPECT_EQ(releases[1].id(), release2.getId()); + + const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track1.getId()); + EXPECT_EQ(tracks[1].id(), track2.getId()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool moreResults {}; + const auto artists {trackList->getArtistsReverse({cluster3.getId()}, std::nullopt, std::nullopt, moreResults)}; + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist1.getId()); + EXPECT_EQ(artists[1].id(), artist2.getId()); + + const auto releases {trackList->getReleasesReverse({cluster3.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release1.getId()); + EXPECT_EQ(releases[1].id(), release2.getId()); + + const auto tracks {trackList->getTracksReverse({cluster3.getId()}, std::nullopt, moreResults)}; + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track1.getId()); + EXPECT_EQ(tracks[1].id(), track2.getId()); + } +} + +TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) +{ + ScopedArtist artist1 {session, "MyArtist1"}; + ScopedArtist artist2 {session, "MyArtist2"}; + ScopedArtist artist3 {session, "MyArtist3"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(artist1->getSimilarArtists().empty()); + EXPECT_TRUE(artist2->getSimilarArtists().empty()); + EXPECT_TRUE(artist3->getSimilarArtists().empty()); + } + + std::list tracks; + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + + if (i < 5) + TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist); + else + { + TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + cluster1.get().modify()->addTrack(tracks.back().get()); + } + + tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); + { + auto transaction {session.createUniqueTransaction()}; + TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + { + auto artists {artist1->getSimilarArtists()}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist2.getId()); + } + + { + auto artists {artist1->getSimilarArtists({TrackArtistLinkType::Artist})}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist2.getId()); + } + + { + auto artists {artist1->getSimilarArtists({TrackArtistLinkType::ReleaseArtist})}; + EXPECT_EQ(artists.empty(), 1); + } + + { + auto artists {artist1->getSimilarArtists({TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist})}; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist2.getId()); + } + + { + auto artists {artist1->getSimilarArtists({TrackArtistLinkType::Composer})}; + EXPECT_TRUE(artists.empty()); + } + + { + auto artists {artist2->getSimilarArtists()}; + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist1.getId()); + EXPECT_EQ(artists[1].id(), artist3.getId()); + } + } +} + +TEST_F(DatabaseFixture, MultipleTracksMultipleReleasesMultiClusters) +{ + ScopedRelease release1 {session, "MyRelease1"}; + ScopedRelease release2 {session, "MyRelease2"}; + ScopedRelease release3 {session, "MyRelease3"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + { + auto transaction {session.createSharedTransaction()}; + EXPECT_TRUE(release1->getSimilarReleases().empty()); + EXPECT_TRUE(release2->getSimilarReleases().empty()); + EXPECT_TRUE(release3->getSimilarReleases().empty()); + } + + std::list tracks; + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + + if (i < 5) + tracks.back().get().modify()->setRelease(release1.get()); + else + { + tracks.back().get().modify()->setRelease(release2.get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + cluster1.get().modify()->addTrack(tracks.back().get()); + } + + tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); + { + auto transaction {session.createUniqueTransaction()}; + tracks.back().get().modify()->setRelease(release3.get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + { + auto releases {release1->getSimilarReleases()}; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release2.getId()); + } + + { + auto releases {release2->getSimilarReleases()}; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release1.getId()); + EXPECT_EQ(releases[1].id(), release3.getId()); + } + } +} + + diff --git a/src/test/database/Common.hpp b/src/test/database/Common.hpp new file mode 100644 index 00000000..82f9115c --- /dev/null +++ b/src/test/database/Common.hpp @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include + +#include "database/Artist.hpp" +#include "database/Cluster.hpp" +#include "database/Db.hpp" +#include "database/Release.hpp" +#include "database/Session.hpp" +#include "database/Track.hpp" +#include "database/TrackArtistLink.hpp" +#include "database/TrackBookmark.hpp" +#include "database/TrackList.hpp" +#include "database/Types.hpp" +#include "database/User.hpp" + +template +class ScopedEntity +{ + public: + template + ScopedEntity(Database::Session& session, Args&& ...args) + : _session {session} + { + auto transaction {_session.createUniqueTransaction()}; + + auto entity {T::create(_session, std::forward(args)...)}; + EXPECT_TRUE(entity); + _id = entity.id(); + } + + ~ScopedEntity() + { + auto transaction {_session.createUniqueTransaction()}; + + auto entity {T::getById(_session, _id)}; + entity.remove(); + } + + ScopedEntity(const ScopedEntity&) = delete; + ScopedEntity(ScopedEntity&&) = delete; + ScopedEntity& operator=(const ScopedEntity&) = delete; + ScopedEntity& operator=(ScopedEntity&&) = delete; + + typename T::pointer lockAndGet() + { + auto transaction {_session.createSharedTransaction()}; + return get(); + } + + typename T::pointer get() + { + _session.checkSharedLocked(); + + auto entity {T::getById(_session, _id)}; + EXPECT_TRUE(entity); + return entity; + } + + typename T::pointer operator->() + { + return get(); + } + + Database::IdType getId() const { return _id; } + + private: + Database::Session& _session; + Database::IdType _id {}; +}; + +using ScopedArtist = ScopedEntity; +using ScopedCluster = ScopedEntity; +using ScopedClusterType = ScopedEntity; +using ScopedRelease = ScopedEntity; +using ScopedTrack = ScopedEntity; +using ScopedTrackBookmark = ScopedEntity; +using ScopedTrackList = ScopedEntity; +using ScopedUser = ScopedEntity; + +class ScopedFileDeleter final +{ + public: + ScopedFileDeleter(const std::filesystem::path& path) : _path {path} {} + ~ScopedFileDeleter() { std::filesystem::remove(_path); } + + ScopedFileDeleter(const ScopedFileDeleter&) = delete; + ScopedFileDeleter(ScopedFileDeleter&&) = delete; + ScopedFileDeleter operator=(const ScopedFileDeleter&) = delete; + ScopedFileDeleter operator=(ScopedFileDeleter&&) = delete; + + private: + const std::filesystem::path _path; +}; + +class TmpDatabase final +{ + public: + Database::Db& getDb() { return _db; } + + private: + const std::filesystem::path _tmpFile {std::tmpnam(nullptr)}; + ScopedFileDeleter fileDeleter {_tmpFile}; + Database::Db _db {_tmpFile}; + +}; + +class DatabaseFixture : public ::testing::Test +{ +public: + ~DatabaseFixture() + { + testDatabaseEmpty(); + } + +public: + static void SetUpTestCase() + { + _tmpDb = std::make_unique(); + { + Database::Session s {_tmpDb->getDb()}; + s.prepareTables(); + s.optimize(); + + // remove default created entries + { + auto transaction {s.createUniqueTransaction()}; + auto clusterTypes {Database::ClusterType::getAll(s)}; + for (auto& clusterType : clusterTypes) + clusterType.remove(); + } + } + } + + static void TearDownTestCase() + { + _tmpDb.reset(); + } + +private: + void testDatabaseEmpty() + { + auto uniqueTransaction {session.createUniqueTransaction()}; + + EXPECT_TRUE(Database::Artist::getAll(session, Database::Artist::SortMethod::ByName).empty()); + EXPECT_TRUE(Database::Cluster::getAll(session).empty()); + EXPECT_TRUE(Database::ClusterType::getAll(session).empty()); + EXPECT_TRUE(Database::Release::getAll(session).empty()); + EXPECT_TRUE(Database::Track::getAll(session).empty()); + EXPECT_TRUE(Database::TrackBookmark::getAll(session).empty()); + EXPECT_TRUE(Database::TrackList::getAll(session).empty()); + EXPECT_TRUE(Database::User::getAll(session).empty()); + } + + static inline std::unique_ptr _tmpDb {}; + +public: + Database::Session session {_tmpDb->getDb()}; +}; + diff --git a/src/test/database/DatabaseTest.cpp b/src/test/database/DatabaseTest.cpp index edcfb4f5..c5d2ad4b 100644 --- a/src/test/database/DatabaseTest.cpp +++ b/src/test/database/DatabaseTest.cpp @@ -17,187 +17,13 @@ * along with LMS. If not, see . */ -#include - -#include #include -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" -#include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackBookmark.hpp" -#include "database/TrackList.hpp" -#include "database/User.hpp" - -#include "utils/StreamLogger.hpp" +#include "Common.hpp" using namespace Database; -#define CHECK(PRED) \ - do \ - { \ - try \ - { \ - if (!(PRED)) \ - { \ - std::string error {"Predicate FAILED '" + std::string {#PRED} + "' at " + __FUNCTION__ + "@l." + std::to_string(__LINE__)}; \ - std::cerr << error << std::endl; \ - throw std::runtime_error {error}; \ - } \ - } \ - catch (Wt::Dbo::Exception& e) \ - { \ - std::cerr << "Caught DBO exception: " < -class ScopedEntity -{ - public: - - template - ScopedEntity(Session& session, Args&& ...args) - : _session {session} - { - auto transaction {_session.createUniqueTransaction()}; - - auto entity {T::create(_session, std::forward(args)...)}; - CHECK(entity); - _id = entity.id(); - } - - ~ScopedEntity() - { - auto transaction {_session.createUniqueTransaction()}; - - auto entity {T::getById(_session, _id)}; - entity.remove(); - } - - ScopedEntity(const ScopedEntity&) = delete; - ScopedEntity(ScopedEntity&&) = delete; - ScopedEntity& operator=(const ScopedEntity&) = delete; - ScopedEntity& operator=(ScopedEntity&&) = delete; - - typename T::pointer lockAndGet() - { - auto transaction {_session.createSharedTransaction()}; - return get(); - } - - typename T::pointer get() - { - _session.checkSharedLocked(); - - auto entity {T::getById(_session, _id)}; - CHECK(entity); - return entity; - } - - typename T::pointer operator->() - { - return get(); - } - - IdType getId() const { return _id; } - - private: - Session& _session; - IdType _id {}; -}; - -using ScopedArtist = ScopedEntity; -using ScopedCluster = ScopedEntity; -using ScopedClusterType = ScopedEntity; -using ScopedRelease = ScopedEntity; -using ScopedTrack = ScopedEntity; -using ScopedTrackBookmark = ScopedEntity; -using ScopedTrackList = ScopedEntity; -using ScopedUser = ScopedEntity; - - -static -void -testRemoveDefaultEntries(Session& session) -{ - { - auto transaction {session.createUniqueTransaction()}; - - auto clusterTypes {ClusterType::getAll(session)}; - for (auto& clusterType : clusterTypes) - clusterType.remove(); - } -} - -static -void -testSingleTrack(Session& session) -{ - { - auto transaction {session.createSharedTransaction()}; - CHECK(Track::getCount(session) == 0); - } - - ScopedTrack track {session, "MyTrackFile"}; - - { - auto transaction {session.createSharedTransaction()}; - - CHECK(Track::getAll(session).size() == 1); - CHECK(Track::getCount(session) == 1); - - } -} - -static -void -testSingleArtist(Session& session) -{ - ScopedArtist artist {session, "MyArtist"}; - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {Artist::getAll(session, Artist::SortMethod::ByName)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - artists = Artist::getAllOrphans(session); - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - } -} - -static -void -testSingleRelease(Session& session) +TEST_F(DatabaseFixture, SingleRelease) { ScopedRelease release {session, "MyRelease"}; @@ -205,273 +31,17 @@ testSingleRelease(Session& session) auto transaction {session.createSharedTransaction()}; auto releases {Release::getAllOrphans(session)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); releases = Release::getAll(session); - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); - - CHECK(release->getDuration() == std::chrono::seconds {0}); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); + EXPECT_EQ(release->getDuration(), std::chrono::seconds {0}); } } -static -void -testSingleCluster(Session& session) -{ - ScopedClusterType clusterType {session, "MyType"}; - - { - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - - { - auto transaction {session.createUniqueTransaction()}; - - auto clusters {Cluster::getAll(session)}; - CHECK(clusters.size() == 1); - CHECK(clusters.front().id() == cluster.getId()); - CHECK(clusters.front()->getType().id() == clusterType.getId()); - - clusters = Cluster::getAllOrphans(session); - CHECK(clusters.size() == 1); - CHECK(clusters.front().id() == cluster.getId()); - - auto clusterTypes {ClusterType::getAll(session)}; - CHECK(clusterTypes.size() == 1); - CHECK(clusterTypes.front().id() == clusterType.getId()); - - clusterTypes = ClusterType::getAllUsed(session); - CHECK(clusterTypes.size() == 1); - CHECK(clusterTypes.front().id() == clusterType.getId()); - - clusterTypes = ClusterType::getAllOrphans(session); - CHECK(clusterTypes.empty()); - } - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto clusterTypes {ClusterType::getAllOrphans(session)}; - CHECK(clusterTypes.size() == 1); - CHECK(clusterTypes.front().id() == clusterType.getId()); - - CHECK(ClusterType::getAllUsed(session).empty()); - } -} - -static -void -testSingleTrackSingleArtist(Session& session) -{ - ScopedTrack track {session, "MyTrack"}; - ScopedArtist artist {session, "MyArtist"}; - - { - auto transaction {session.createUniqueTransaction()}; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Artist::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {track->getArtists({TrackArtistLinkType::Artist})}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - CHECK(artist->getReleaseCount() == 0); - - CHECK(track->getArtistLinks().size() == 1); - auto artistLink {track->getArtistLinks().front()}; - CHECK(artistLink->getTrack().id() == track.getId()); - CHECK(artistLink->getArtist().id() == artist.getId()); - - CHECK(track->getArtists({TrackArtistLinkType::Artist}).size() == 1); - CHECK(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty()); - CHECK(track->getArtists({}).size() == 1); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto tracks {artist->getTracks()}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track.getId()); - - CHECK(artist->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); - CHECK(artist->getTracks(TrackArtistLinkType::Artist).size() == 1); - } -} - -static -void -testSingleTrackSingleArtistMultiRoles(Session& session) -{ - ScopedTrack track {session, "MyTrack"}; - ScopedArtist artist {session, "MyArtist"}; - { - auto transaction {session.createUniqueTransaction()}; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Artist::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - bool hasMore{}; - CHECK(Artist::getByFilter(session, {}, {}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::Artist, Artist::SortMethod::ByName, std::nullopt, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::ReleaseArtist, Artist::SortMethod::ByName, std::nullopt, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::Writer, Artist::SortMethod::ByName, std::nullopt, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLinkType::Composer, Artist::SortMethod::ByName, std::nullopt, hasMore).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {track->getArtists({TrackArtistLinkType::Artist})}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - artists = track->getArtists({TrackArtistLinkType::ReleaseArtist}); - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - CHECK(track->getArtistLinks().size() == 3); - - CHECK(artist->getTracks().size() == 1); - CHECK(artist->getTracks({TrackArtistLinkType::ReleaseArtist}).size() == 1); - CHECK(artist->getTracks({TrackArtistLinkType::Artist}).size() == 1); - CHECK(artist->getTracks({TrackArtistLinkType::Writer}).size() == 1); - } -} - -static -void -testSingleTrackMultiArtists(Session& session) -{ - ScopedTrack track {session, "track"}; - ScopedArtist artist1 {session, "artist1"}; - ScopedArtist artist2 {session, "artist2"}; - CHECK(artist1.getId() != artist2.getId()); - - { - auto transaction {session.createUniqueTransaction()}; - - TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Artist::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {track->getArtists({TrackArtistLinkType::Artist})}; - CHECK(artists.size() == 2); - CHECK((artists[0].id() == artist1.getId() && artists[1].id() == artist2.getId()) - || (artists[0].id() == artist2.getId() && artists[1].id() == artist1.getId())); - - CHECK(track->getArtists({}).size() == 2); - CHECK(track->getArtists({TrackArtistLinkType::Artist}).size() == 2); - CHECK(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty()); - CHECK(Artist::getAll(session, Artist::SortMethod::ByName).size() == 2); - CHECK(Artist::getAllIds(session).size() == 2); - } - - { - auto transaction {session.createUniqueTransaction()}; - - CHECK(artist1->getTracks().front() == track.get()); - CHECK(artist2->getTracks().front() == track.get()); - - CHECK(artist1->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); - CHECK(artist1->getTracks(TrackArtistLinkType::Artist).size() == 1); - CHECK(artist2->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); - CHECK(artist2->getTracks(TrackArtistLinkType::Artist).size() == 1); - } -} - -static -void -testSingleArtistSearchByName(Session& session) -{ - ScopedArtist artist {session, "AAA"}; - ScopedTrack track {session, "MyTrack"}; // filters does not work on orphans - - { - auto transaction {session.createUniqueTransaction()}; - artist.get().modify()->setSortName("ZZZ"); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool more {}; - CHECK(Artist::getByFilter(session, {}, {"N"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more).empty()); - - const auto artistsByAAA {Artist::Artist::getByFilter(session, {}, {"A"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; - CHECK(artistsByAAA.size() == 1); - CHECK(artistsByAAA.front().id() == artist.getId()); - - const auto artistsByZZZ {Artist::Artist::getByFilter(session, {}, {"Z"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, more)}; - CHECK(artistsByZZZ.size() == 1); - CHECK(artistsByZZZ.front().id() == artist.getId()); - - CHECK(Artist::getByName(session, "NNN").empty()); - } -} - -static -void -testMultiArtistsSortMethod(Session& session) -{ - ScopedArtist artistA {session, "artistA"}; - ScopedArtist artistB {session, "artistB"}; - - { - auto transaction {session.createUniqueTransaction()}; - - artistA.get().modify()->setSortName("sortNameB"); - artistB.get().modify()->setSortName("sortNameA"); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto allArtistsByName {Artist::getAll(session, Artist::SortMethod::ByName)}; - auto allArtistsBySortName {Artist::getAll(session, Artist::SortMethod::BySortName)}; - - CHECK(allArtistsByName.size() == 2); - CHECK(allArtistsByName.front().id() == artistA.getId()); - CHECK(allArtistsByName.back().id() == artistB.getId()); - - CHECK(allArtistsBySortName.size() == 2); - CHECK(allArtistsBySortName.front().id() == artistB.getId()); - CHECK(allArtistsBySortName.back().id() == artistA.getId()); - } -} - -static -void -testSingleTrackSingleRelease(Session& session) +TEST_F(DatabaseFixture, SingleTrackSingleRelease) { ScopedRelease release {session, "MyRelease"}; @@ -487,60 +57,125 @@ testSingleTrackSingleRelease(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(Release::getAllOrphans(session).empty()); + EXPECT_TRUE(Release::getAllOrphans(session).empty()); - CHECK(release->getTracks().size() == 1); - CHECK(release->getTracksCount() == 1); - CHECK(release->getTracks().front().id() == track.getId()); + EXPECT_EQ(release->getTracksCount(), 1); + ASSERT_EQ(release->getTracks().size(), 1); + EXPECT_EQ(release->getTracks().front().id(), track.getId()); } { auto transaction {session.createUniqueTransaction()}; - CHECK(track->getRelease()); - CHECK(track->getRelease().id() == release.getId()); + ASSERT_TRUE(track->getRelease()); + EXPECT_EQ(track->getRelease().id(), release.getId()); } { auto transaction {session.createUniqueTransaction()}; auto tracks {Track::getByNameAndReleaseName(session, "MyTrackName", "MyReleaseName")}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track.getId()); + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track.getId()); } { auto transaction {session.createUniqueTransaction()}; auto tracks {Track::getByNameAndReleaseName(session, "MyTrackName", "MyReleaseFoo")}; - CHECK(tracks.size() == 0); + EXPECT_EQ(tracks.size(), 0); } { auto transaction {session.createUniqueTransaction()}; auto tracks {Track::getByNameAndReleaseName(session, "MyTrackFoo", "MyReleaseName")}; - CHECK(tracks.size() == 0); + EXPECT_EQ(tracks.size(), 0); } } { auto transaction {session.createUniqueTransaction()}; - CHECK(release->getTracks().empty()); + EXPECT_TRUE(release->getTracks().empty()); auto releases {Release::getAllOrphans(session)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); } } -static -void -testMultiTracksSingleReleaseTotalDiscTrack(Session& session) +TEST_F(DatabaseFixture, MulitpleReleaseSearchByName) +{ + ScopedRelease release1 {session, "MyRelease"}; + ScopedRelease release2 {session, "MyRelease%"}; + ScopedRelease release3 {session, "%MyRelease"}; + ScopedRelease release4 {session, "MyRelease%Foo"}; + ScopedRelease release5 {session, "Foo%MyRelease"}; + ScopedRelease release6 {session, "_yRelease"}; + + // filters does not work on orphans + ScopedTrack track1 {session, "MyTrack"}; + ScopedTrack track2 {session, "MyTrack"}; + ScopedTrack track3 {session, "MyTrack"}; + ScopedTrack track4 {session, "MyTrack"}; + ScopedTrack track5 {session, "MyTrack"}; + ScopedTrack track6 {session, "MyTrack"}; + + { + auto transaction {session.createUniqueTransaction()}; + + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + track3.get().modify()->setRelease(release3.get()); + track4.get().modify()->setRelease(release4.get()); + track5.get().modify()->setRelease(release5.get()); + track6.get().modify()->setRelease(release6.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool more; + { + const auto releases {Release::getByFilter(session, {}, {"Release"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 6); + } + + { + const auto releases {Release::getByFilter(session, {}, {"MyRelease"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 5); + EXPECT_TRUE(std::none_of(std::cbegin(releases), std::cend(releases), [&](const Release::pointer& release) { return release.id() == release6.getId(); })); + } + { + const auto releases {Release::getByFilter(session, {}, {"MyRelease%"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release2.getId()); + EXPECT_EQ(releases[1].id(), release4.getId()); + } + { + const auto releases {Release::getByFilter(session, {}, {"%MyRelease"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release3.getId()); + EXPECT_EQ(releases[1].id(), release5.getId()); + } + { + const auto releases {Release::getByFilter(session, {}, {"Foo%MyRelease"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 1); + EXPECT_EQ(releases[0].id(), release5.getId()); + } + { + const auto releases {Release::getByFilter(session, {}, {"MyRelease%Foo"}, std::nullopt, more)}; + EXPECT_EQ(releases.size(), 1); + EXPECT_EQ(releases[0].id(), release4.getId()); + } + } +} + +TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack) { ScopedRelease release1 {session, "MyRelease"}; { auto transaction {session.createSharedTransaction()}; - CHECK(!release1->getTotalTrack()); - CHECK(!release1->getTotalDisc()); + EXPECT_FALSE(release1->getTotalTrack()); + EXPECT_FALSE(release1->getTotalDisc()); } ScopedTrack track1 {session, "MyTrack"}; @@ -553,8 +188,8 @@ testMultiTracksSingleReleaseTotalDiscTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(!release1->getTotalTrack()); - CHECK(!release1->getTotalDisc()); + EXPECT_FALSE(release1->getTotalTrack()); + EXPECT_FALSE(release1->getTotalDisc()); } { @@ -567,8 +202,10 @@ testMultiTracksSingleReleaseTotalDiscTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(release1->getTotalTrack() && *release1->getTotalTrack() == 36); - CHECK(release1->getTotalDisc() && *release1->getTotalDisc() == 6); + ASSERT_TRUE(release1->getTotalTrack()); + EXPECT_EQ(*release1->getTotalTrack(), 36); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 6); } ScopedTrack track2 {session, "MyTrack2"}; @@ -583,16 +220,18 @@ testMultiTracksSingleReleaseTotalDiscTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(release1->getTotalTrack() && *release1->getTotalTrack() == 37); - CHECK(release1->getTotalDisc() && *release1->getTotalDisc() == 67); + ASSERT_TRUE(release1->getTotalTrack()); + EXPECT_EQ(*release1->getTotalTrack(), 37); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 67); } ScopedRelease release2 {session, "MyRelease2"}; { auto transaction {session.createSharedTransaction()}; - CHECK(!release2->getTotalTrack()); - CHECK(!release2->getTotalDisc()); + EXPECT_FALSE(release2->getTotalTrack()); + EXPECT_FALSE(release2->getTotalDisc()); } ScopedTrack track3 {session, "MyTrack3"}; @@ -606,16 +245,18 @@ testMultiTracksSingleReleaseTotalDiscTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(release1->getTotalTrack() && *release1->getTotalTrack() == 37); - CHECK(release1->getTotalDisc() && *release1->getTotalDisc() == 67); - CHECK(release2->getTotalTrack() && *release2->getTotalTrack() == 7); - CHECK(release2->getTotalDisc() && *release2->getTotalDisc() == 5); + ASSERT_TRUE(release1->getTotalTrack()); + EXPECT_EQ(*release1->getTotalTrack(), 37); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 67); + ASSERT_TRUE(release2->getTotalTrack()); + EXPECT_EQ(*release2->getTotalTrack(), 7); + ASSERT_TRUE(release2->getTotalDisc()); + EXPECT_EQ(*release2->getTotalDisc(), 5); } } -static -void -testMultiTracksSingleReleaseFirstTrack(Session& session) +TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack) { ScopedRelease release1 {session, "MyRelease1"}; ScopedRelease release2 {session, "MyRelease2"}; @@ -628,8 +269,8 @@ testMultiTracksSingleReleaseFirstTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(!release1->getFirstTrack()); - CHECK(!release2->getFirstTrack()); + EXPECT_FALSE(release1->getFirstTrack()); + EXPECT_FALSE(release2->getFirstTrack()); } { @@ -652,500 +293,15 @@ testMultiTracksSingleReleaseFirstTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(release1->getFirstTrack()); - CHECK(release2->getFirstTrack()); + EXPECT_TRUE(release1->getFirstTrack()); + EXPECT_TRUE(release2->getFirstTrack()); - CHECK(release1->getFirstTrack().id() == track1A.getId()); - CHECK(release2->getFirstTrack().id() == track2B.getId()); + EXPECT_EQ(release1->getFirstTrack().id(), track1A.getId()); + EXPECT_EQ(release2->getFirstTrack().id(), track2B.getId()); } } -static -void -testSingleTrackSingleCluster(Session& session) -{ - ScopedTrack track {session, "MyTrack"}; - ScopedClusterType clusterType {session, "MyClusterType"}; - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Cluster::getAllOrphans(session).empty()); - auto clusterTypes {ClusterType::getAllOrphans(session)}; - CHECK(clusterTypes.size() ==1); - CHECK(clusterTypes.front().id() == clusterType.getId()); - } - - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; - - { - auto transaction {session.createSharedTransaction()}; - auto clusters {Cluster::getAllOrphans(session)}; - CHECK(clusters.size() == 2); - CHECK(track->getClusters().empty()); - CHECK(track->getClusterIds().empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Track::getAllIdsWithClusters(session).empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - cluster1.get().modify()->addTrack(track.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - auto tracks {Track::getAllIdsWithClusters(session)}; - CHECK(tracks.size() == 1); - CHECK(tracks.front() == track.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - auto clusters {Cluster::getAllOrphans(session)}; - CHECK(clusters.size() == 1); - CHECK(clusters.front().id() == cluster2.getId()); - - CHECK(ClusterType::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto tracks {Track::getByClusters(session, {cluster1.getId()})}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track.getId()); - - tracks = Track::getByClusters(session, {cluster2.getId()}); - CHECK(tracks.empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto clusters {track->getClusters()}; - CHECK(clusters.size() == 1); - CHECK(clusters.front().id() == cluster1.getId()); - - auto clusterIds {track->getClusterIds()}; - CHECK(clusterIds.size() == 1); - CHECK(clusterIds.front() == cluster1.getId()); - } -} - -static -void -testMultipleTracksSingleCluster(Session& session) -{ - std::list tracks; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - - for (std::size_t i {}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(tracks.back().get()); - } - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Cluster::getAllOrphans(session).empty()); - - CHECK(cluster->getTracksCount() == tracks.size()); - - for (auto trackCluster : cluster->getTracks()) - { - auto it {std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackCluster.id() == track.getId(); })}; - CHECK(it != std::cend(tracks)); - } - } -} - -static -void -testMultipleTracksMultipleClustersTopRelease(Session& session) -{ - ScopedClusterType clusterType {session, "ClusterType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "Cluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "Cluster2"}; - ScopedCluster cluster3 {session, clusterType.lockAndGet(), "Cluster3"}; - ScopedTrack trackA {session, "TrackA"}; - ScopedTrack trackB {session, "TrackB"}; - ScopedTrack trackC {session, "TrackC"}; - ScopedRelease releaseA {session, "ReleaseA"}; - ScopedRelease releaseB {session, "ReleaseB"}; - ScopedRelease releaseC {session, "ReleaseC"}; - - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "TrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; - - { - auto transaction {session.createSharedTransaction()}; - - CHECK(trackList->getDuration() == std::chrono::seconds {0}); - } - - { - auto transaction {session.createUniqueTransaction()}; - - cluster1.get().modify()->addTrack(trackA.get()); - cluster2.get().modify()->addTrack(trackB.get()); - cluster2.get().modify()->addTrack(trackC.get()); - cluster3.get().modify()->addTrack(trackC.get()); - - trackA.get().modify()->setRelease(releaseA.get()); - trackB.get().modify()->setRelease(releaseB.get()); - trackC.get().modify()->setRelease(releaseC.get()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - TrackListEntry::create(session, trackA.get(), trackList.get()); - TrackListEntry::create(session, trackB.get(), trackList.get()); - TrackListEntry::create(session, trackB.get(), trackList.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool hasMore; - const auto releases{trackList->getTopReleases({}, std::nullopt, hasMore)}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == releaseB.getId()); - CHECK(releases[1].id() == releaseA.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool hasMore; - auto releases{trackList->getTopReleases({cluster1.getId()}, std::nullopt, hasMore)}; - CHECK(releases.size() == 1); - CHECK(releases[0].id() == releaseA.getId()); - - releases = trackList->getTopReleases({cluster2.getId()}, std::nullopt, hasMore); - CHECK(releases.size() == 1); - CHECK(releases[0].id() == releaseB.getId()); - - releases = trackList->getTopReleases({cluster2.getId(), cluster1.getId()}, std::nullopt, hasMore); - CHECK(releases.empty()); - - releases = trackList->getTopReleases({cluster2.getId(), cluster3.getId()}, std::nullopt, hasMore); - CHECK(releases.empty()); - } - - - { - auto transaction {session.createUniqueTransaction()}; - - TrackListEntry::create(session, trackC.get(), trackList.get()); - TrackListEntry::create(session, trackC.get(), trackList.get()); - TrackListEntry::create(session, trackC.get(), trackList.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool hasMore; - auto releases {trackList->getTopReleases({cluster2.getId(), cluster3.getId()}, std::nullopt, hasMore)}; - CHECK(releases.size() == 1); - CHECK(releases[0].id() == releaseC.getId()); - - releases = trackList->getTopReleases({cluster2.getId()}, std::nullopt, hasMore); - CHECK(releases.size() == 2); - CHECK(releases[0].id() == releaseC.getId()); - CHECK(releases[1].id() == releaseB.getId()); - } - - -} - -static -void -testMultipleTracksSingleClusterSimilarity(Session& session) -{ - std::list tracks; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyClusterType"}; - - for (std::size_t i {}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction {session.createUniqueTransaction()}; - cluster.get().modify()->addTrack(tracks.back().get()); - } - } - - { - auto transaction {session.createSharedTransaction()}; - - const auto similarTracks {Track::getSimilarTracks(session, {tracks.front().getId()})}; - CHECK(similarTracks.size() == tracks.size() - 1); - for (auto similarTrack : similarTracks) - { - CHECK(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), - [&](const ScopedTrack& track) - { - return similarTrack.id() == track.getId(); - }) != std::cend(tracks)); - } - } -} - -static -void -testMultipleTracksMultipleClustersSimilarity(Session& session) -{ - std::list tracks; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; - - for (std::size_t i {}; i < 5; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction {session.createUniqueTransaction()}; - cluster1.get().modify()->addTrack(tracks.back().get()); - } - } - - for (std::size_t i {5}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction {session.createUniqueTransaction()}; - cluster1.get().modify()->addTrack(tracks.back().get()); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - } - - { - auto transaction {session.createSharedTransaction()}; - - { - auto similarTracks {Track::getSimilarTracks(session, {tracks.back().getId()}, 0, 4)}; - CHECK(similarTracks.size() == 4); - for (auto similarTrack : similarTracks) - CHECK(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const ScopedTrack& track) { return similarTrack.id() == track.getId(); }) != std::cend(tracks)); - } - - { - auto similarTracks {Track::getSimilarTracks(session, {tracks.front().getId()})}; - CHECK(similarTracks.size() == tracks.size() - 1); - for (auto similarTrack : similarTracks) - CHECK(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const ScopedTrack& track) { return similarTrack.id() == track.getId(); }) != std::cend(tracks)); - } - } -} - -static -void -testSingleTrackSingleReleaseSingleCluster(Session& session) -{ - ScopedTrack track {session, "MyTrackFile"}; - ScopedRelease release {session, "MyRelease"}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster {session, clusterType .lockAndGet(), "MyCluster"}; - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Release::getAllIdsWithClusters(session).empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - track.get().modify()->setRelease(release.get()); - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - auto releases {Release::getAllIdsWithClusters(session)}; - CHECK(releases.size() == 1); - CHECK(releases.front() == release.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - CHECK(Cluster::getAllOrphans(session).empty()); - CHECK(Release::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto releases {Release::getByClusters(session, {cluster.getId()})}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - CHECK(cluster->getReleasesCount() == 1); - CHECK(cluster->getTracksCount() == 1); - } -} - -static -void -testSingleTrackSingleArtistMultiClusters(Session& session) -{ - ScopedTrack track {session, "MyTrackFile"}; - ScopedArtist artist {session, "MyArtist"}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "Cluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "Cluster2"}; - ScopedCluster cluster3 {session, clusterType.lockAndGet(), "Cluster3"}; - { - auto transaction {session.createUniqueTransaction()}; - - auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist)}; - cluster1.get().modify()->addTrack(track.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(ClusterType::getAllOrphans(session).empty()); - CHECK(Cluster::getAllOrphans(session).size() == 2); - CHECK(Release::getAllOrphans(session).empty()); - CHECK(Artist::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(track->getClusters().size() == 1); - CHECK(track->getClusterIds().size() == 1); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::SortMethod::ByName)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - CHECK(Artist::getByClusters(session, {cluster2.getId()}, Artist::SortMethod::ByName).empty()); - CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::SortMethod::ByName).empty()); - - cluster2.get().modify()->addTrack(track.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::SortMethod::ByName)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - artists = Artist::getByClusters(session, {cluster2.getId()}, Artist::SortMethod::ByName); - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}, Artist::SortMethod::ByName); - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::SortMethod::ByName).empty()); - } -} - -static -void -testSingleTrackSingleArtistMultiRolesMultiClusters(Session& session) -{ - ScopedTrack track {session, "MyTrackFile"}; - ScopedArtist artist {session, "MyArtist"}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - - { - auto transaction {session.createUniqueTransaction()}; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Cluster::getAllOrphans(session).empty()); - CHECK(Release::getAllOrphans(session).empty()); - CHECK(Artist::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::SortMethod::ByName)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - } -} - -static -void -testMultiTracksSingleArtistMultiClusters(Session& session) -{ - constexpr std::size_t nbTracks {10}; - constexpr std::size_t nbClusters {5}; - - std::list tracks; - std::list clusters; - ScopedArtist artist {session, "MyArtist"}; - ScopedClusterType clusterType {session, "MyType"}; - - for (std::size_t i {}; i < nbClusters; ++i) - clusters.emplace_back(session, clusterType.lockAndGet(), "MyCluster" + std::to_string(i)); - - for (std::size_t i {}; i < nbTracks ; ++i) - { - tracks.emplace_back(session, "MyTrackFile" + std::to_string(i)); - - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist); - - for (auto& cluster : clusters) - cluster.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction {session.createSharedTransaction()}; - CHECK(Cluster::getAllOrphans(session).empty()); - CHECK(Artist::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - - std::set clusterIds; - std::transform(std::cbegin(clusters), std::cend(clusters), std::inserter(clusterIds, std::begin(clusterIds)), [](const ScopedCluster& cluster) { return cluster.getId(); }); - - auto artists {Artist::getByClusters(session, clusterIds, Artist::SortMethod::ByName)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - } -} - -static -void -testMultiTracksSingleArtistSingleRelease(Session& session) +TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease) { constexpr std::size_t nbTracks {10}; std::list tracks; @@ -1164,25 +320,22 @@ testMultiTracksSingleArtistSingleRelease(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(Release::getAllOrphans(session).empty()); - CHECK(Artist::getAllOrphans(session).empty()); + EXPECT_TRUE(Release::getAllOrphans(session).empty()); + EXPECT_TRUE(Artist::getAllOrphans(session).empty()); } { auto transaction {session.createSharedTransaction()}; - CHECK(artist->getReleaseCount() == 1); - CHECK(artist->getReleases().size() == 1); - CHECK(artist->getReleases().front().id() == release.getId()); + EXPECT_EQ(artist->getReleaseCount(), 1); + ASSERT_EQ(artist->getReleases().size(), 1); + EXPECT_EQ(artist->getReleases().front().id(), release.getId()); - CHECK(release->getTracks().size() == nbTracks); + EXPECT_EQ(release->getTracks().size(), nbTracks); } - } -static -void -testSingleTrackSingleReleaseSingleArtist(Session& session) +TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist) { ScopedTrack track {session, "MyTrack"}; ScopedRelease release {session, "MyRelease"}; @@ -1199,127 +352,48 @@ testSingleTrackSingleReleaseSingleArtist(Session& session) auto transaction {session.createUniqueTransaction()}; auto releases {artist->getReleases()}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); - CHECK(artist->getReleaseCount() == 1); + EXPECT_EQ(artist->getReleaseCount(), 1); auto artists {release->getArtists()}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); + ASSERT_EQ(artists.size(), 1); + ASSERT_EQ(artists.front().id(), artist.getId()); } } -static -void -testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session) +TEST_F(DatabaseFixture, SingleUser) { - ScopedTrack track {session, "MyTrack"}; - ScopedRelease release {session, "MyRelease"}; - ScopedArtist artist {session, "MyArtist"}; - ScopedClusterType clusterType {session, "MyType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - { auto transaction {session.createSharedTransaction()}; - CHECK(Artist::getAllIdsWithClusters(session).empty()); + EXPECT_TRUE(User::getAll(session).empty()); + EXPECT_TRUE(User::getAllIds(session).empty()); } - { - auto transaction {session.createUniqueTransaction()}; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - track.get().modify()->setRelease(release.get()); - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - CHECK(Cluster::getAllOrphans(session).empty()); - CHECK(ClusterType::getAllOrphans(session).empty()); - CHECK(Artist::getAllOrphans(session).empty()); - CHECK(Release::getAllOrphans(session).empty()); - } - - { - auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getAllIdsWithClusters(session)}; - CHECK(artists.size() == 1); - CHECK(artists.front() == artist.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::SortMethod::ByName)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - - auto releases {artist->getReleases()}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); - - releases = artist->getReleases({cluster.getId()}); - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); - } -} - -static -void -testSingleTrackSingleReleaseSingleArtistMultiClusters(Session& session) -{ - ScopedTrack track {session, "MyTrack"}; - ScopedRelease release {session, "MyRelease"}; - ScopedArtist artist {session, "MyArtist"}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; - - { - auto transaction {session.createUniqueTransaction()}; - - auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist)}; - track.get().modify()->setRelease(release.get()); - cluster1.get().modify()->addTrack(track.get()); - cluster2.get().modify()->addTrack(track.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto releases {artist->getReleases()}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); - - releases = artist->getReleases({cluster1.getId(), cluster2.getId()}); - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); - } -} - -static -void -testSingleUser(Session& session) -{ ScopedUser user {session, "MyUser"}; { auto transaction {session.createSharedTransaction()}; - CHECK(user->getQueuedTrackList(session)->getCount() == 0); + EXPECT_EQ(user->getQueuedTrackList(session)->getCount(), 0); + EXPECT_EQ(User::getAll(session).size(), 1); + EXPECT_EQ(User::getAllIds(session).size(), 1); } } -static -void -testSingleStarredArtist(Session& session) +TEST_F(DatabaseFixture, SingleStarredArtist) { ScopedArtist artist {session, "MyArtist"}; ScopedTrack track {session, "MyTrack"}; ScopedUser user {session, "MyUser"}; + { + auto transaction {session.createUniqueTransaction()}; + + EXPECT_FALSE(user->hasStarredArtist(artist.get())); + } + { auto transaction {session.createUniqueTransaction()}; @@ -1330,24 +404,28 @@ testSingleStarredArtist(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(user->hasStarredArtist(artist.get())); + EXPECT_TRUE(user->hasStarredArtist(artist.get())); bool hasMore {}; auto artists {Artist::getStarred(session, user.get(), {}, std::nullopt, Artist::SortMethod::BySortName, std::nullopt, hasMore)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist.getId()); - CHECK(hasMore == false); + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist.getId()); + EXPECT_FALSE(hasMore); } } -static -void -testSingleStarredRelease(Session& session) +TEST_F(DatabaseFixture, SingleStarredRelease) { ScopedRelease release {session, "MyRelease"}; ScopedTrack track {session, "MyTrack"}; ScopedUser user {session, "MyUser"}; + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_FALSE(user->hasStarredRelease(release.get())); + } + { auto transaction {session.createUniqueTransaction()}; @@ -1358,23 +436,27 @@ testSingleStarredRelease(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(user->hasStarredRelease(release.get())); + EXPECT_TRUE(user->hasStarredRelease(release.get())); bool hasMore {}; auto releases {Release::getStarred(session, user.get(), {}, std::nullopt, hasMore)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release.getId()); - CHECK(hasMore == false); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release.getId()); + EXPECT_FALSE(hasMore); } } -static -void -testSingleStarredTrack(Session& session) +TEST_F(DatabaseFixture, SingleStarredTrack) { ScopedTrack track {session, "MyTrack"}; ScopedUser user {session, "MyUser"}; + { + auto transaction {session.createUniqueTransaction()}; + + EXPECT_FALSE(user->hasStarredTrack(track.get())); + } + { auto transaction {session.createUniqueTransaction()}; @@ -1384,19 +466,17 @@ testSingleStarredTrack(Session& session) { auto transaction {session.createUniqueTransaction()}; - CHECK(user->hasStarredTrack(track.get())); + EXPECT_TRUE(user->hasStarredTrack(track.get())); bool hasMore {}; auto tracks {Track::getStarred(session, user.get(), {}, std::nullopt, hasMore)}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track.getId()); - CHECK(hasMore == false); + ASSERT_EQ(tracks.size(), 1); + EXPECT_EQ(tracks.front().id(), track.getId()); + EXPECT_FALSE(hasMore); } } -static -void -testSingleTrackList(Session& session) +TEST_F(DatabaseFixture, SingleTrackList) { ScopedUser user {session, "MyUser"}; ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; @@ -1405,15 +485,12 @@ testSingleTrackList(Session& session) auto transaction {session.createSharedTransaction()}; auto trackLists {TrackList::getAll(session, user.get(), TrackList::Type::Playlist)}; - CHECK(trackLists.size() == 1); - CHECK(trackLists.front().id() == trackList.getId()); + ASSERT_EQ(trackLists.size(), 1); + EXPECT_EQ(trackLists.front().id(), trackList.getId()); } - } -static -void -testSingleTrackListMultipleTrack(Session& session) +TEST_F(DatabaseFixture, SingleTrackListMultipleTrack) { ScopedUser user {session, "MyUser"}; ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; @@ -1430,15 +507,14 @@ testSingleTrackListMultipleTrack(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(trackList->getCount() == tracks.size()); + ASSERT_EQ(trackList->getCount(), tracks.size()); const auto trackIds {trackList->getTrackIds()}; for (auto trackId : trackIds) - CHECK(std::any_of(std::cbegin(tracks), std::cend(tracks), [trackId](const ScopedTrack& track) { return track.getId() == trackId; })); + EXPECT_TRUE(std::any_of(std::cbegin(tracks), std::cend(tracks), [trackId](const ScopedTrack& track) { return track.getId() == trackId; })); } } -void -testSingleTrackListMultipleTrackDateTime(Session& session) +TEST_F(DatabaseFixture, SingleTrackListMultipleTrackDateTime) { ScopedUser user {session, "MyUser"}; ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; @@ -1459,108 +535,16 @@ testSingleTrackListMultipleTrackDateTime(Session& session) bool moreResults; const auto tracks {trackList.get()->getTracksReverse({}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 3); - CHECK(tracks.front().id() == track3.getId()); - CHECK(tracks.back().id() == track2.getId()); + ASSERT_EQ(tracks.size(), 3); + EXPECT_EQ(tracks.front().id(), track3.getId()); + EXPECT_EQ(tracks.back().id(), track2.getId()); } } -static -void -testSingleTrackListMultipleTrackSingleCluster(Session& session) +TEST_F(DatabaseFixture, SingleTrackListMultipleTrackRecentlyPlayed) { ScopedUser user {session, "MyUser"}; ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - std::list tracks; - - for (std::size_t i {}; i < 20; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction {session.createUniqueTransaction()}; - - if (i < 5) - TrackListEntry::create(session, tracks.back().get(), trackList.get()); - - if (i < 10) - cluster.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - const auto similarTracks {trackList->getSimilarTracks()}; - CHECK(similarTracks.size() == 5); - - for (auto similarTrack : similarTracks) - CHECK(std::any_of(std::next(std::cbegin(tracks), 5), std::cend(tracks), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); - } -} - -static -void -testSingleTrackListMultipleTrackMultiClusters(Session& session) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; - std::list tracks; - - for (std::size_t i {}; i < 20; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction {session.createUniqueTransaction()}; - - if (i < 5) - TrackListEntry::create(session, tracks.back().get(), trackList.get()); - - if (i < 10) - { - cluster1.get().modify()->addTrack(tracks.back().get()); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - else if (i < 15) - { - cluster1.get().modify()->addTrack(tracks.back().get()); - } - } - - { - auto transaction {session.createSharedTransaction()}; - - { - const auto similarTracks {trackList->getSimilarTracks(0, 5)}; - CHECK(similarTracks.size() == 5); - - for (auto similarTrack : similarTracks) - CHECK(std::any_of(std::next(std::cbegin(tracks), 5), std::next(std::cbegin(tracks), 10), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); - } - - { - const auto similarTracks {trackList->getSimilarTracks(5, 10)}; - CHECK(similarTracks.size() == 5); - - for (auto similarTrack : similarTracks) - CHECK(std::any_of(std::next(std::cbegin(tracks), 10), std::next(std::cbegin(tracks), 15), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); - } - - CHECK(trackList->getSimilarTracks(10, 10).empty()); - - } -} - -static -void -testSingleTrackListMultipleTrackRecentlyPlayed(Session& session) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; - ScopedClusterType clusterType {session, "MyClusterType"}; ScopedTrack track1 {session, "MyTrack1"}; ScopedTrack track2 {session, "MyTrack1"}; ScopedArtist artist1 {session, "MyArtist1"}; @@ -1583,9 +567,9 @@ testSingleTrackListMultipleTrackRecentlyPlayed(Session& session) auto transaction {session.createSharedTransaction()}; bool moreResults {}; - CHECK(trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults).empty()); - CHECK(trackList->getReleasesReverse({}, std::nullopt, moreResults).empty()); - CHECK(trackList->getTracksReverse({}, std::nullopt, moreResults).empty()); + EXPECT_TRUE(trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults).empty()); + EXPECT_TRUE(trackList->getReleasesReverse({}, std::nullopt, moreResults).empty()); + EXPECT_TRUE(trackList->getTracksReverse({}, std::nullopt, moreResults).empty()); } { @@ -1599,15 +583,15 @@ testSingleTrackListMultipleTrackRecentlyPlayed(Session& session) bool moreResults {}; const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist1.getId()); + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front().id(), artist1.getId()); const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release1.getId()); + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front().id(), release1.getId()); const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 1); + EXPECT_EQ(tracks.size(), 1); } { @@ -1621,19 +605,19 @@ testSingleTrackListMultipleTrackRecentlyPlayed(Session& session) bool moreResults {}; const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 2); - CHECK(artists[0].id() == artist2.getId()); - CHECK(artists[1].id() == artist1.getId()); + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist2.getId()); + EXPECT_EQ(artists[1].id(), artist1.getId()); const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == release2.getId()); - CHECK(releases[1].id() == release1.getId()); + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release2.getId()); + EXPECT_EQ(releases[1].id(), release1.getId()); const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 2); - CHECK(tracks[0].id() == track2.getId()); - CHECK(tracks[1].id() == track1.getId()); + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track2.getId()); + EXPECT_EQ(tracks[1].id(), track1.getId()); } { @@ -1647,425 +631,23 @@ testSingleTrackListMultipleTrackRecentlyPlayed(Session& session) bool moreResults {}; const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 2); - CHECK(artists[0].id() == artist1.getId()); - CHECK(artists[1].id() == artist2.getId()); + ASSERT_EQ(artists.size(), 2); + EXPECT_EQ(artists[0].id(), artist1.getId()); + EXPECT_EQ(artists[1].id(), artist2.getId()); const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == release1.getId()); - CHECK(releases[1].id() == release2.getId()); + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0].id(), release1.getId()); + EXPECT_EQ(releases[1].id(), release2.getId()); const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 2); - CHECK(tracks[0].id() == track1.getId()); - CHECK(tracks[1].id() == track2.getId()); + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track1.getId()); + EXPECT_EQ(tracks[1].id(), track2.getId()); } } -static -void -testSingleTrackListMultipleTrackMultiClustersRecentlyPlayed(Session& session) -{ - ScopedUser user {session, "MyUser"}; - ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; - ScopedCluster cluster3 {session, clusterType.lockAndGet(), "MyCluster3"}; - ScopedTrack track1 {session, "MyTrack1"}; - ScopedTrack track2 {session, "MyTrack1"}; - ScopedArtist artist1 {session, "MyArtist1"}; - ScopedArtist artist2 {session, "MyArtist2"}; - ScopedRelease release1 {session, "MyRelease1"}; - ScopedRelease release2 {session, "MyRelease2"}; - - const Wt::WDateTime now {Wt::WDateTime::currentDateTime()}; - - { - auto transaction {session.createUniqueTransaction()}; - - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); - - cluster1.get().modify()->addTrack(track1.get()); - cluster2.get().modify()->addTrack(track2.get()); - cluster3.get().modify()->addTrack(track1.get()); - cluster3.get().modify()->addTrack(track2.get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - CHECK(trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults).empty()); - CHECK(trackList->getReleasesReverse({}, std::nullopt, moreResults).empty()); - CHECK(trackList->getTracksReverse({}, std::nullopt, moreResults).empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - TrackListEntry::create(session, track1.get(), trackList.get(), now); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist1.getId()); - - const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release1.getId()); - - const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({cluster1.getId()}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist1.getId()); - - const auto releases {trackList->getReleasesReverse({cluster1.getId()}, std::nullopt, moreResults)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release1.getId()); - - const auto tracks {trackList->getTracksReverse({cluster1.getId()}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({cluster3.getId()}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist1.getId()); - - const auto releases {trackList->getReleasesReverse({cluster3.getId()}, std::nullopt, moreResults)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release1.getId()); - - const auto tracks {trackList->getTracksReverse({cluster3.getId()}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({cluster1.getId()}, TrackArtistLinkType::Artist, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({}, TrackArtistLinkType::Artist, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - CHECK(trackList->getArtistsReverse({cluster2.getId()}, std::nullopt, std::nullopt, moreResults).empty()); - CHECK(trackList->getReleasesReverse({cluster2.getId()}, std::nullopt, moreResults).empty()); - CHECK(trackList->getTracksReverse({cluster2.getId()}, std::nullopt, moreResults).empty()); - - CHECK(trackList->getArtistsReverse({}, TrackArtistLinkType::ReleaseArtist, std::nullopt, moreResults).empty()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - TrackListEntry::create(session, track2.get(), trackList.get(), now.addSecs(1)); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 2); - CHECK(artists[0].id() == artist2.getId()); - CHECK(artists[1].id() == artist1.getId()); - - const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == release2.getId()); - CHECK(releases[1].id() == release1.getId()); - - const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 2); - CHECK(tracks[0].id() == track2.getId()); - CHECK(tracks[1].id() == track1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({cluster3.getId()}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 2); - CHECK(artists[0].id() == artist2.getId()); - CHECK(artists[1].id() == artist1.getId()); - - const auto releases {trackList->getReleasesReverse({cluster3.getId()}, std::nullopt, moreResults)}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == release2.getId()); - CHECK(releases[1].id() == release1.getId()); - - const auto tracks {trackList->getTracksReverse({cluster3.getId()}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 2); - CHECK(tracks[0].id() == track2.getId()); - CHECK(tracks[1].id() == track1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({cluster1.getId()}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist1.getId()); - - const auto releases {trackList->getReleasesReverse({cluster1.getId()}, std::nullopt, moreResults)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release1.getId()); - - const auto tracks {trackList->getTracksReverse({cluster1.getId()}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track1.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({cluster2.getId()}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist2.getId()); - - const auto releases {trackList->getReleasesReverse({cluster2.getId()}, std::nullopt, moreResults)}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release2.getId()); - - const auto tracks {trackList->getTracksReverse({cluster2.getId()}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == track2.getId()); - } - - { - auto transaction {session.createUniqueTransaction()}; - - TrackListEntry::create(session, track1.get(), trackList.get(), now.addSecs(2)); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 2); - CHECK(artists[0].id() == artist1.getId()); - CHECK(artists[1].id() == artist2.getId()); - - const auto releases {trackList->getReleasesReverse({}, std::nullopt, moreResults)}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == release1.getId()); - CHECK(releases[1].id() == release2.getId()); - - const auto tracks {trackList->getTracksReverse({}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 2); - CHECK(tracks[0].id() == track1.getId()); - CHECK(tracks[1].id() == track2.getId()); - } - - { - auto transaction {session.createSharedTransaction()}; - - bool moreResults {}; - const auto artists {trackList->getArtistsReverse({cluster3.getId()}, std::nullopt, std::nullopt, moreResults)}; - CHECK(artists.size() == 2); - CHECK(artists[0].id() == artist1.getId()); - CHECK(artists[1].id() == artist2.getId()); - - const auto releases {trackList->getReleasesReverse({cluster3.getId()}, std::nullopt, moreResults)}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == release1.getId()); - CHECK(releases[1].id() == release2.getId()); - - const auto tracks {trackList->getTracksReverse({cluster3.getId()}, std::nullopt, moreResults)}; - CHECK(tracks.size() == 2); - CHECK(tracks[0].id() == track1.getId()); - CHECK(tracks[1].id() == track2.getId()); - } -} - - -static -void -testMultipleTracksMultipleArtistsMultiClusters(Session& session) -{ - ScopedArtist artist1 {session, "MyArtist1"}; - ScopedArtist artist2 {session, "MyArtist2"}; - ScopedArtist artist3 {session, "MyArtist3"}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; - - { - auto transaction {session.createSharedTransaction()}; - CHECK(artist1->getSimilarArtists().empty()); - CHECK(artist2->getSimilarArtists().empty()); - CHECK(artist3->getSimilarArtists().empty()); - } - - std::list tracks; - for (std::size_t i {}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction {session.createUniqueTransaction()}; - - if (i < 5) - TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist); - else - { - TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - - cluster1.get().modify()->addTrack(tracks.back().get()); - } - - tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); - { - auto transaction {session.createUniqueTransaction()}; - TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - { - auto artists {artist1->getSimilarArtists()}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist2.getId()); - } - - { - auto artists {artist1->getSimilarArtists({TrackArtistLinkType::Artist})}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist2.getId()); - } - - { - auto artists {artist1->getSimilarArtists({TrackArtistLinkType::ReleaseArtist})}; - CHECK(artists.empty() == 1); - } - - { - auto artists {artist1->getSimilarArtists({TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist})}; - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artist2.getId()); - } - - { - auto artists {artist1->getSimilarArtists({TrackArtistLinkType::Composer})}; - CHECK(artists.empty()); - } - - { - auto artists {artist2->getSimilarArtists()}; - CHECK(artists.size() == 2); - CHECK(artists[0].id() == artist1.getId()); - CHECK(artists[1].id() == artist3.getId()); - } - } -} - -static -void -testMultipleTracksMultipleReleasesMultiClusters(Session& session) -{ - ScopedRelease release1 {session, "MyRelease1"}; - ScopedRelease release2 {session, "MyRelease2"}; - ScopedRelease release3 {session, "MyRelease3"}; - ScopedClusterType clusterType {session, "MyClusterType"}; - ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; - ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; - - { - auto transaction {session.createSharedTransaction()}; - CHECK(release1->getSimilarReleases().empty()); - CHECK(release2->getSimilarReleases().empty()); - CHECK(release3->getSimilarReleases().empty()); - } - - std::list tracks; - for (std::size_t i {}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction {session.createUniqueTransaction()}; - - if (i < 5) - tracks.back().get().modify()->setRelease(release1.get()); - else - { - tracks.back().get().modify()->setRelease(release2.get()); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - - cluster1.get().modify()->addTrack(tracks.back().get()); - } - - tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); - { - auto transaction {session.createUniqueTransaction()}; - tracks.back().get().modify()->setRelease(release3.get()); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction {session.createSharedTransaction()}; - - { - auto releases {release1->getSimilarReleases()}; - CHECK(releases.size() == 1); - CHECK(releases.front().id() == release2.getId()); - } - - { - auto releases {release2->getSimilarReleases()}; - CHECK(releases.size() == 2); - CHECK(releases[0].id() == release1.getId()); - CHECK(releases[1].id() == release3.getId()); - } - } -} - -static -void -testSingleTrackSingleUserSingleBookmark(Session& session) +TEST_F(DatabaseFixture, SingleTrackSingleUserSingleBookmark) { ScopedTrack track {session, "MyTrack"}; ScopedUser user {session, "MyUser"}; @@ -2081,127 +663,26 @@ testSingleTrackSingleUserSingleBookmark(Session& session) { auto transaction {session.createSharedTransaction()}; - CHECK(TrackBookmark::getAll(session).size() == 1); + EXPECT_EQ(TrackBookmark::getAll(session).size(), 1); const auto bookmarks {TrackBookmark::getByUser(session, user.get())}; - CHECK(bookmarks.size() == 1); - CHECK(bookmarks.back() == bookmark.get()); + ASSERT_EQ(bookmarks.size(), 1); + EXPECT_EQ(bookmarks.back(), bookmark.get()); } { auto transaction {session.createSharedTransaction()}; auto userBookmark {TrackBookmark::getByUser(session, user.get(), track.get())}; - CHECK(userBookmark); - CHECK(userBookmark == bookmark.get()); + ASSERT_TRUE(userBookmark); + EXPECT_EQ(userBookmark, bookmark.get()); - CHECK(userBookmark->getOffset() == std::chrono::milliseconds {5}); - CHECK(userBookmark->getComment() == "MyComment"); + EXPECT_EQ(userBookmark->getOffset(), std::chrono::milliseconds {5}); + EXPECT_EQ(userBookmark->getComment(), "MyComment"); } } -static -void -testDatabaseEmpty(Session& session) +int main(int argc, char **argv) { - auto uniqueTransaction {session.createUniqueTransaction()}; - - CHECK(Artist::getAll(session, Artist::SortMethod::ByName).empty()); - CHECK(Cluster::getAll(session).empty()); - CHECK(ClusterType::getAll(session).empty()); - CHECK(Release::getAll(session).empty()); - CHECK(Track::getAll(session).empty()); - CHECK(TrackBookmark::getAll(session).empty()); - CHECK(TrackList::getAll(session).empty()); - CHECK(User::getAll(session).empty()); -} - -int main() -{ - try - { - // log to stdout - Service logger {std::make_unique(std::cout)}; - - const std::filesystem::path tmpFile {std::tmpnam(nullptr)}; - ScopedFileDeleter tmpFileDeleter {tmpFile}; - - std::cout << "Database test file: '" << tmpFile.string() << "'" << std::endl; - - Database::Db db {tmpFile}; - Database::Session session {db}; - session.prepareTables(); - - auto runTest = [&session](const std::string& name, std::function testFunc) - { - std::cout << "Running test '" << name << "'..." << std::endl; - testFunc(session); - testDatabaseEmpty(session); - std::cout << "Running test '" << name << "': SUCCESS" << std::endl; - }; - -#define RUN_TEST(test) runTest(#test, test) - - // Special test to remove any default created entries - RUN_TEST(testRemoveDefaultEntries); - - RUN_TEST(testSingleTrack); - RUN_TEST(testSingleArtist); - RUN_TEST(testSingleRelease); - RUN_TEST(testSingleCluster); - - RUN_TEST(testSingleTrackSingleArtist); - RUN_TEST(testSingleTrackSingleArtistMultiRoles); - RUN_TEST(testSingleTrackMultiArtists); - - RUN_TEST(testSingleArtistSearchByName); - RUN_TEST(testMultiArtistsSortMethod); - - RUN_TEST(testSingleTrackSingleRelease); - RUN_TEST(testMultiTracksSingleReleaseTotalDiscTrack); - RUN_TEST(testMultiTracksSingleReleaseFirstTrack); - - RUN_TEST(testSingleTrackSingleCluster); - RUN_TEST(testMultipleTracksSingleCluster); - - RUN_TEST(testMultipleTracksMultipleClustersTopRelease); - - RUN_TEST(testMultipleTracksSingleClusterSimilarity); - RUN_TEST(testMultipleTracksMultipleClustersSimilarity); - - RUN_TEST(testSingleTrackSingleReleaseSingleCluster); - RUN_TEST(testSingleTrackSingleArtistMultiClusters); - RUN_TEST(testSingleTrackSingleArtistMultiRolesMultiClusters); - RUN_TEST(testMultiTracksSingleArtistMultiClusters); - RUN_TEST(testMultiTracksSingleArtistSingleRelease); - - RUN_TEST(testSingleTrackSingleReleaseSingleArtist); - - RUN_TEST(testSingleTrackSingleReleaseSingleArtistSingleCluster); - RUN_TEST(testSingleTrackSingleReleaseSingleArtistMultiClusters); - - RUN_TEST(testSingleUser); - - RUN_TEST(testSingleStarredArtist); - RUN_TEST(testSingleStarredRelease); - RUN_TEST(testSingleStarredTrack); - - RUN_TEST(testSingleTrackList); - RUN_TEST(testSingleTrackListMultipleTrack); - RUN_TEST(testSingleTrackListMultipleTrackDateTime); - RUN_TEST(testSingleTrackListMultipleTrackSingleCluster); - RUN_TEST(testSingleTrackListMultipleTrackMultiClusters); - RUN_TEST(testSingleTrackListMultipleTrackRecentlyPlayed); - RUN_TEST(testSingleTrackListMultipleTrackMultiClustersRecentlyPlayed); - RUN_TEST(testMultipleTracksMultipleArtistsMultiClusters); - RUN_TEST(testMultipleTracksMultipleReleasesMultiClusters); - - RUN_TEST(testSingleTrackSingleUserSingleBookmark); - } - catch (std::exception& e) - { - std::cerr << "Caught exception: " << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/test/database/Track.cpp b/src/test/database/Track.cpp new file mode 100644 index 00000000..1223fc9c --- /dev/null +++ b/src/test/database/Track.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include "Common.hpp" + +#include + +using namespace Database; + +TEST_F(DatabaseFixture, SingleTrack) +{ + { + auto transaction {session.createSharedTransaction()}; + EXPECT_EQ(Track::getCount(session), 0); + } + + ScopedTrack track {session, "MyTrackFile"}; + + { + auto transaction {session.createSharedTransaction()}; + + EXPECT_EQ(Track::getAll(session).size(), 1); + EXPECT_EQ(Track::getCount(session), 1); + } +} + +TEST_F(DatabaseFixture, MultipleTracksSearchByFilter) +{ + + ScopedTrack track1 {session, ""}; + ScopedTrack track2 {session, ""}; + ScopedTrack track3 {session, ""}; + ScopedTrack track4 {session, ""}; + ScopedTrack track5 {session, ""}; + ScopedTrack track6 {session, ""}; + + { + auto transaction {session.createUniqueTransaction()}; + track1.get().modify()->setName("MyTrack"); + track2.get().modify()->setName("MyTrack%"); + track3.get().modify()->setName("MyTrack%Foo"); + track4.get().modify()->setName("%MyTrack"); + track5.get().modify()->setName("Foo%MyTrack"); + track6.get().modify()->setName("M_Track"); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool more; + { + const auto tracks {Track::getByFilter(session, {}, {"Track"}, std::nullopt, more)}; + EXPECT_EQ(tracks.size(), 6); + } + { + const auto tracks {Track::getByFilter(session, {}, {"MyTrack"}, std::nullopt, more)}; + EXPECT_EQ(tracks.size(), 5); + EXPECT_TRUE(std::none_of(std::cbegin(tracks), std::cend(tracks), [&](const Track::pointer& track) { return track.id() == track6.getId(); })); + } + { + const auto tracks {Track::getByFilter(session, {}, {"MyTrack%"}, std::nullopt, more)}; + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track2.getId()); + EXPECT_EQ(tracks[1].id(), track3.getId()); + } + { + const auto tracks {Track::getByFilter(session, {}, {"%MyTrack"}, std::nullopt, more)}; + ASSERT_EQ(tracks.size(), 2); + EXPECT_EQ(tracks[0].id(), track4.getId()); + EXPECT_EQ(tracks[1].id(), track5.getId()); + } + } +} + diff --git a/src/test/som/CMakeLists.txt b/src/test/som/CMakeLists.txt index aaaf43d4..4c409913 100644 --- a/src/test/som/CMakeLists.txt +++ b/src/test/som/CMakeLists.txt @@ -1,3 +1,4 @@ +include(GoogleTest) add_executable(test-som SomTest.cpp @@ -5,7 +6,8 @@ add_executable(test-som target_link_libraries(test-som PRIVATE lmssom + GTest::GTest ) -add_test(NAME som COMMAND test-som) +gtest_discover_tests(test-som) diff --git a/src/test/som/SomTest.cpp b/src/test/som/SomTest.cpp index bfbf05a5..c2ef65db 100644 --- a/src/test/som/SomTest.cpp +++ b/src/test/som/SomTest.cpp @@ -17,25 +17,40 @@ * along with LMS. If not, see . */ -#include -#include -#include +#include #include "som/DataNormalizer.hpp" #include "som/Network.hpp" using namespace SOM; -int main() +static constexpr InputVector::value_type EPSILON = 0.01; + +TEST(som, Matrix) { - static const InputVector::value_type EPSILON = 0.01; { Matrix testMatrix {2, 2, 123}; - assert((testMatrix[{0,0}] == 123)); - assert((testMatrix[{0,1}] == 123)); - assert((testMatrix[{1,0}] == 123)); - assert((testMatrix[{1,1}] == 123)); + { + const Position pos {0, 0}; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos {0, 1}; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos {1, 0}; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos {1, 1}; + EXPECT_EQ(testMatrix[pos], 123); + } } +} + +TEST(som, InputVector) +{ { InputVector test1 {2}; @@ -48,69 +63,72 @@ int main() InputVector test3 {test1}; test3 += test2; - assert(std::abs(test3[0] - 1) < EPSILON); - assert(std::abs(test3[1] - 1) < EPSILON); + EXPECT_LT(std::abs(test3[0] - 1), EPSILON); + EXPECT_LT(std::abs(test3[1] - 1), EPSILON); + } +} + +TEST(som, Network) +{ + Network network {2, 2, 1}; + + const InputVector weights {1, 1}; + std::vector trainData + { + { 1, 50 }, + { 1, 100 }, + { 1, 150 }, + { 1, 200 }, + }; + + DataNormalizer normalizer {1}; + normalizer.computeNormalizationFactors(trainData); + for (auto& data: trainData) + normalizer.normalizeData(data); + + network.dump(std::cout); + network.train(trainData, 20); + network.dump(std::cout); + + auto distFunc {network.getDistanceFunc()}; + + EXPECT_LT((std::abs(distFunc({1, 0}, {1, 1}, weights) - 1)), EPSILON); + EXPECT_LT((std::abs(distFunc({1, 0}, {1, 2}, weights) - 4)), EPSILON); + EXPECT_LT(std::abs(distFunc({1, 0}, {1, 0.33}, weights) - distFunc({1, 0.66}, {1, 1.}, weights)), EPSILON); + + { + std::unordered_set positions; + for (const InputVector& data : trainData) + positions.insert(network.getClosestRefVectorPosition(data)); + + EXPECT_EQ(positions.size(), 4); } { - Network network {2, 2, 1}; - - const InputVector weights {1, 1}; - std::vector trainData { - { 1, 50 }, - { 1, 100 }, - { 1, 150 }, - { 1, 200 }, - }; - - DataNormalizer normalizer {1}; - normalizer.computeNormalizationFactors(trainData); - for (auto& data: trainData) - normalizer.normalizeData(data); - - network.dump(std::cout); - network.train(trainData, 20); - network.dump(std::cout); - - std::cout << "MEAN dist = " << network.computeRefVectorsDistanceMean() << std::endl; - std::cout << "MEDIAN dist = " << network.computeRefVectorsDistanceMedian() << std::endl; - - auto distFunc {network.getDistanceFunc()}; - - assert((std::abs(distFunc({1, 0}, {1, 1}, weights) - 1) < EPSILON)); - assert((std::abs(distFunc({1, 0}, {1, 2}, weights) - 4) < EPSILON)); - assert((std::abs(distFunc({1, 0}, {1, 0.33}, weights) - distFunc({1, 0.66}, {1, 1.}, weights)) < EPSILON)); - + Position pos {network.getClosestRefVectorPosition(InputVector{1, 0.66})}; + for (std::size_t i {}; i < 40; ++i) { - std::unordered_set positions; - for (const InputVector& data : trainData) - positions.insert(network.getClosestRefVectorPosition(data)); - assert(positions.size() == 4); + InputVector input {1, 130 + static_cast(i) }; + normalizer.normalizeData(input); + + EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); } - - { - Position pos {network.getClosestRefVectorPosition(InputVector{1, 0.66})}; - for (std::size_t i {}; i < 40; ++i) - { - InputVector input {1, 130 + static_cast(i) }; - normalizer.normalizeData(input); - - assert( network.getClosestRefVectorPosition(input) == pos); - } - } - - { - Position pos {network.getClosestRefVectorPosition(InputVector{1, 1})}; - for (std::size_t i {}; i < 40; ++i) - { - InputVector input {1, 180 + static_cast(i) }; - normalizer.normalizeData(input); - - assert( network.getClosestRefVectorPosition(input) == pos); - } - } - } - return 0; + { + Position pos {network.getClosestRefVectorPosition(InputVector{1, 1})}; + for (std::size_t i {}; i < 40; ++i) + { + InputVector input {1, 180 + static_cast(i) }; + normalizer.normalizeData(input); + + EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); + } + } +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/test/utils/CMakeLists.txt b/src/test/utils/CMakeLists.txt index 806f856f..1b355f9b 100644 --- a/src/test/utils/CMakeLists.txt +++ b/src/test/utils/CMakeLists.txt @@ -1,12 +1,16 @@ +include(GoogleTest) add_executable(test-utils - UtilsTest.cpp + String.cpp + RecursiveSharedMutex.cpp + Utils.cpp ) target_link_libraries(test-utils PRIVATE lmsutils Threads::Threads + GTest::GTest ) -add_test(NAME utils COMMAND test-utils) +gtest_discover_tests(test-utils) diff --git a/src/test/utils/UtilsTest.cpp b/src/test/utils/RecursiveSharedMutex.cpp similarity index 50% rename from src/test/utils/UtilsTest.cpp rename to src/test/utils/RecursiveSharedMutex.cpp index 0b7f2ea8..0728b91f 100644 --- a/src/test/utils/UtilsTest.cpp +++ b/src/test/utils/RecursiveSharedMutex.cpp @@ -18,85 +18,15 @@ */ #include -#include #include -#include #include #include -#include + +#include #include "utils/RecursiveSharedMutex.hpp" -#include "utils/String.hpp" - -void -testStrings() -{ - { - const std::string test{"a"}; - - const std::vector strings {StringUtils::splitString(test, "")}; - assert(strings.size() == 1); - assert(strings.front() == "a"); - } - - { - const std::string test{"a b"}; - - const std::vector strings {StringUtils::splitString(test, "|")}; - assert(strings.size() == 1); - assert(strings.front() == "a b"); - } - - { - const std::string test{" a"}; - - const std::vector strings {StringUtils::splitString(test, " ")}; - assert(strings.size() == 1); - assert(strings.front() == "a"); - } - - { - const std::string test{"a "}; - - const std::vector strings {StringUtils::splitString(test, " ")}; - assert(strings.size() == 1); - assert(strings.front() == "a"); - } - - { - const std::string test{"a b"}; - - const std::vector strings {StringUtils::splitString(test, " ")}; - assert(strings.size() == 2); - assert(strings.front() == "a"); - assert(strings.back() == "b"); - } - - { - const std::string test{"a b,c|defgh "}; - - const std::vector strings {StringUtils::splitString(test, " ,|")}; - assert(strings.size() == 4); - assert(strings[0] == "a"); - assert(strings[1] == "b"); - assert(strings[2] == "c"); - assert(strings[3] == "defgh"); - } - - { - assert(StringUtils::escapeString("", "*", ' ') == ""); - assert(StringUtils::escapeString("", "", ' ') == ""); - assert(StringUtils::escapeString("a", "", ' ') == "a"); - assert(StringUtils::escapeString("*", "*", '_') == "_*"); - assert(StringUtils::escapeString("*a*", "*", '_') == "_*a_*"); - assert(StringUtils::escapeString("*a|", "*|", '_') == "_*a_|"); - assert(StringUtils::escapeString("**||", "*|", '_') == "_*_*_|_|"); - } -} - -void -testSharedMutex() +TEST(RecursiveSharedMutex, SingleThreaded) { { RecursiveSharedMutex mutex; @@ -124,7 +54,10 @@ testSharedMutex() std::shared_lock lock2 {mutex}; } } +} +TEST(RecursiveSharedMutex, MultiThreaded) +{ { constexpr std::size_t nbThreads {10}; std::vector threads; @@ -171,20 +104,3 @@ testSharedMutex() t.join(); } } - - -int main() -{ - try - { - testStrings(); - testSharedMutex(); - } - catch (std::exception& e) - { - std::cerr << "Caught exception: " << e.what(); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/src/test/utils/String.cpp b/src/test/utils/String.cpp new file mode 100644 index 00000000..9ece8f13 --- /dev/null +++ b/src/test/utils/String.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include + +#include "utils/String.hpp" + +TEST(StringUtils, splitString) +{ + { + const std::string test{"a"}; + + const std::vector strings {StringUtils::splitString(test, "")}; + ASSERT_EQ(strings.size(), 1); + EXPECT_EQ(strings.front() , "a"); + } + + { + const std::string test{"a b"}; + + const std::vector strings {StringUtils::splitString(test, "|")}; + ASSERT_EQ(strings.size(), 1); + EXPECT_EQ(strings.front(), "a b"); + } + + { + const std::string test{" a"}; + + const std::vector strings {StringUtils::splitString(test, " ")}; + ASSERT_EQ(strings.size(), 1); + EXPECT_EQ(strings.front(), "a"); + } + + { + const std::string test{"a "}; + + const std::vector strings {StringUtils::splitString(test, " ")}; + ASSERT_EQ(strings.size(), 1); + EXPECT_EQ(strings.front(), "a"); + } + + { + const std::string test{"a b"}; + + const std::vector strings {StringUtils::splitString(test, " ")}; + ASSERT_EQ(strings.size(), 2); + EXPECT_EQ(strings.front(), "a"); + EXPECT_EQ(strings.back(), "b"); + } + + { + const std::string test{"a b,c|defgh "}; + + const std::vector strings {StringUtils::splitString(test, " ,|")}; + ASSERT_EQ(strings.size(), 4); + EXPECT_EQ(strings[0], "a"); + EXPECT_EQ(strings[1], "b"); + EXPECT_EQ(strings[2], "c"); + EXPECT_EQ(strings[3], "defgh"); + } +} + +TEST(StringUtils, escapeString) +{ + EXPECT_EQ(StringUtils::escapeString("", "*", ' '), ""); + EXPECT_EQ(StringUtils::escapeString("", "", ' '), ""); + EXPECT_EQ(StringUtils::escapeString("a", "", ' '), "a"); + EXPECT_EQ(StringUtils::escapeString("*", "*", '_'), "_*"); + EXPECT_EQ(StringUtils::escapeString("*a*", "*", '_'), "_*a_*"); + EXPECT_EQ(StringUtils::escapeString("*a|", "*|", '_'), "_*a_|"); + EXPECT_EQ(StringUtils::escapeString("**||", "*|", '_'), "_*_*_|_|"); +} + diff --git a/src/test/utils/Utils.cpp b/src/test/utils/Utils.cpp new file mode 100644 index 00000000..88325e8b --- /dev/null +++ b/src/test/utils/Utils.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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. + * + * LMS 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 LMS. If not, see . + */ + +#include + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +