/* * 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 #include #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Db.hpp" #include "database/TrackList.hpp" #include "database/Release.hpp" #include "database/Track.hpp" #include "database/User.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 (std::exception& e) \ { \ std::cerr << "Exception caught: " << e.what() << std::endl; \ throw; \ } \ } while (0) 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; }; template 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 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) { ScopedTrack track {session, "MyTrackFile"}; { auto transaction {session.createUniqueTransaction()}; CHECK(Track::getAll(session).size() == 1); } } static void testSingleArtist(Session& session) { ScopedArtist artist {session, "MyArtist"}; { auto transaction {session.createSharedTransaction()}; auto artists {Artist::getAll(session)}; 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) { ScopedRelease release {session, "MyRelease"}; { auto transaction {session.createSharedTransaction()}; auto releases {Release::getAllOrphans(session)}; CHECK(releases.size() == 1); CHECK(releases.front().id() == release.getId()); releases = Release::getAll(session); CHECK(releases.size() == 1); CHECK(releases.front().id() == release.getId()); } } 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::getAllOrphans(session); CHECK(clusterTypes.empty()); } } { auto transaction {session.createUniqueTransaction()}; auto clusterTypes {ClusterType::getAllOrphans(session)}; CHECK(clusterTypes.size() == 1); CHECK(clusterTypes.front().id() == clusterType.getId()); } } static void testSingleTrackSingleArtist(Session& session) { ScopedTrack track {session, "MyTrack"}; ScopedArtist artist {session, "MyArtist"}; { auto transaction {session.createUniqueTransaction()}; TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist); } { auto transaction {session.createSharedTransaction()}; CHECK(Artist::getAllOrphans(session).empty()); } { auto transaction {session.createSharedTransaction()}; auto artists {track->getArtists()}; 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(TrackArtistLink::Type::Artist).size() == 1); CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).empty()); } { auto transaction {session.createUniqueTransaction()}; auto tracks {artist->getTracks()}; CHECK(tracks.size() == 1); CHECK(tracks.front().id() == track.getId()); CHECK(artist->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist->getTracks(TrackArtistLink::Type::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(), TrackArtistLink::Type::Artist); TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::ReleaseArtist); TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Writer); } { auto transaction {session.createSharedTransaction()}; CHECK(Artist::getAllOrphans(session).empty()); } { auto transaction {session.createSharedTransaction()}; auto artists {track->getArtists(TrackArtistLink::Type::Artist)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); artists = track->getArtists(TrackArtistLink::Type::ReleaseArtist); CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); CHECK(track->getArtistLinks().size() == 3); CHECK(artist->getTracks().size() == 1); CHECK(artist->getTracks(TrackArtistLink::Type::ReleaseArtist).size() == 1); CHECK(artist->getTracks(TrackArtistLink::Type::Artist).size() == 1); CHECK(artist->getTracks(TrackArtistLink::Type::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(), TrackArtistLink::Type::Artist); TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLink::Type::Artist); } { auto transaction {session.createSharedTransaction()}; CHECK(Artist::getAllOrphans(session).empty()); } { auto transaction {session.createSharedTransaction()}; auto artists {track->getArtists()}; 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(TrackArtistLink::Type::Artist).size() == 2); CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(Artist::getAll(session).size() == 2); } { auto transaction {session.createUniqueTransaction()}; CHECK(artist1->getTracks().front() == track.get()); CHECK(artist2->getTracks().front() == track.get()); CHECK(artist1->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist1->getTracks(TrackArtistLink::Type::Artist).size() == 1); CHECK(artist2->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist2->getTracks(TrackArtistLink::Type::Artist).size() == 1); } } static void testSingleTrackSingleRelease(Session& session) { ScopedRelease release {session, "MyRelease"}; { ScopedTrack track {session, "MyTrack"}; { auto transaction {session.createUniqueTransaction()}; track.get().modify()->setRelease(release.get()); } { auto transaction {session.createSharedTransaction()}; CHECK(Release::getAllOrphans(session).empty()); CHECK(release->getTracks().size() == 1); CHECK(release->getTracksCount() == 1); CHECK(release->getTracks().front().id() == track.getId()); } { auto transaction {session.createUniqueTransaction()}; CHECK(track->getRelease()); CHECK(track->getRelease().id() == release.getId()); } } { auto transaction {session.createUniqueTransaction()}; CHECK(release->getTracks().empty()); auto releases {Release::getAllOrphans(session)}; CHECK(releases.size() == 1); CHECK(releases.front().id() == release.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); } { auto transaction {session.createUniqueTransaction()}; cluster1.get().modify()->addTrack(track.get()); } { 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()); } } static void testMultipleTracksSingleCluster(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()}; 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 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.createUniqueTransaction()}; track.get().modify()->setRelease(release.get()); cluster.get().modify()->addTrack(track.get()); } { 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(), TrackArtistLink::Type::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()}; auto artists {Artist::getByClusters(session, {cluster1.getId()})}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); CHECK(Artist::getByClusters(session, {cluster2.getId()}).empty()); CHECK(Artist::getByClusters(session, {cluster3.getId()}).empty()); cluster2.get().modify()->addTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; auto artists {Artist::getByClusters(session, {cluster1.getId()})}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); artists = Artist::getByClusters(session, {cluster2.getId()}); CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}); CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); CHECK(Artist::getByClusters(session, {cluster3.getId()}).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(), TrackArtistLink::Type::Artist); TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::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()})}; 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(), TrackArtistLink::Type::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)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); } } static void testMultiTracksSingleArtistSingleRelease(Session& session) { constexpr std::size_t nbTracks {10}; std::list tracks; ScopedArtist artist {session, "MyArtst"}; ScopedRelease release {session, "MyRelease"}; for (std::size_t i {}; i < nbTracks; ++i) { tracks.emplace_back(session, "MyTrack" + std::to_string(i)); auto transaction {session.createUniqueTransaction()}; TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLink::Type::Artist); tracks.back().get().modify()->setRelease(release.get()); } { auto transaction {session.createSharedTransaction()}; CHECK(Release::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); } { auto transaction {session.createSharedTransaction()}; CHECK(artist->getReleaseCount() == 1); CHECK(artist->getReleases().size() == 1); CHECK(artist->getReleases().front().id() == release.getId()); CHECK(release->getTracks().size() == nbTracks); } } static void testSingleTrackSingleReleaseSingleArtist(Session& session) { ScopedTrack track {session, "MyTrack"}; ScopedRelease release {session, "MyRelease"}; ScopedArtist artist {session, "MyArtist"}; { auto transaction {session.createUniqueTransaction()}; auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist)}; track.get().modify()->setRelease(release.get()); } { auto transaction {session.createUniqueTransaction()}; auto releases {artist->getReleases()}; CHECK(releases.size() == 1); CHECK(releases.front().id() == release.getId()); CHECK(artist->getReleaseCount() == 1); auto artists {release->getArtists()}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); } } static void testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session) { 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.createUniqueTransaction()}; TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::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::getByClusters(session, {cluster.getId()})}; 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(), TrackArtistLink::Type::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", User::PasswordHash {}}; { auto transaction {session.createSharedTransaction()}; CHECK(user->getPlayedTrackList(session)->getCount() == 0); CHECK(user->getPlayedTrackList(session)->getTopTracks(1).empty()); CHECK(user->getPlayedTrackList(session)->getTopArtists(1).empty()); CHECK(user->getPlayedTrackList(session)->getTopReleases(1).empty()); CHECK(user->getQueuedTrackList(session)->getCount() == 0); } } static void testSingleStarredArtist(Session& session) { ScopedArtist artist {session, "MyArtist"}; ScopedUser user {session, "MyUser", User::PasswordHash {}}; { auto transaction {session.createSharedTransaction()}; user.get().modify()->starArtist(artist.get()); } { auto transaction {session.createSharedTransaction()}; auto artists {user->getStarredArtists()}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); CHECK(user->hasStarredArtist(artist.get())); } } static void testSingleStarredRelease(Session& session) { ScopedRelease release {session, "MyRelease"}; ScopedUser user {session, "MyUser", User::PasswordHash {}}; { auto transaction {session.createUniqueTransaction()}; user.get().modify()->starRelease(release.get()); } { auto transaction {session.createSharedTransaction()}; auto releases {user->getStarredReleases()}; CHECK(releases.size() == 1); CHECK(releases.front().id() == release.getId()); CHECK(user->hasStarredRelease(release.get())); } } static void testSingleStarredTrack(Session& session) { ScopedTrack track {session, "MyTrack"}; ScopedUser user {session, "MyUser", User::PasswordHash {}}; { auto transaction {session.createUniqueTransaction()}; user.get().modify()->starTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; auto tracks {user->getStarredTracks()}; CHECK(tracks.size() == 1); CHECK(tracks.front().id() == track.getId()); CHECK(user->hasStarredTrack(track.get())); } } static void testSingleTrackList(Session& session) { ScopedUser user {session, "MyUser", User::PasswordHash {}}; ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; { auto transaction {session.createSharedTransaction()}; auto trackLists {TrackList::getAll(session, user.get(), TrackList::Type::Playlist)}; CHECK(trackLists.size() == 1); CHECK(trackLists.front().id() == trackList.getId()); } } static void testSingleTrackListMultipleTrack(Session& session) { ScopedUser user {session, "MyUser", User::PasswordHash {}}; ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; std::list tracks; for (std::size_t i {}; i < 10; ++i) { tracks.emplace_back(session, "MyTrack" + std::to_string(i)); auto transaction {session.createUniqueTransaction()}; TrackListEntry::create(session, tracks.back().get(), trackList.get()); } { auto transaction {session.createSharedTransaction()}; CHECK(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; })); } } static void testSingleTrackListMultipleTrackSingleCluster(Session& session) { ScopedUser user {session, "MyUser", User::PasswordHash {}}; 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", User::PasswordHash {}}; 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 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(), TrackArtistLink::Type::Artist); else { TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLink::Type::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(), TrackArtistLink::Type::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 {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 testDatabaseEmpty(Session& session) { auto uniqueTransaction {session.createUniqueTransaction()}; CHECK(Artist::getAll(session).empty()); CHECK(Cluster::getAll(session).empty()); CHECK(ClusterType::getAll(session).empty()); CHECK(Release::getAll(session).empty()); CHECK(Track::getAll(session).empty()); CHECK(TrackList::getAll(session).empty()); CHECK(User::getAll(session).empty()); } int main() { try { const std::filesystem::path tmpFile {std::tmpnam(nullptr)}; ScopedFileDeleter tmpFileDeleter {tmpFile}; std::cout << "Database test file: '" << tmpFile.string() << "'" << std::endl; for (std::size_t i = 0; i < 2; ++i) { Database::Db db {tmpFile}; std::unique_ptr session {db.createSession()}; 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(testSingleTrackSingleRelease); RUN_TEST(testSingleTrackSingleCluster); RUN_TEST(testMultipleTracksSingleCluster); 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(testSingleTrackListMultipleTrackSingleCluster); RUN_TEST(testSingleTrackListMultipleTrackMultiClusters); RUN_TEST(testMultipleTracksMultipleArtistsMultiClusters); RUN_TEST(testMultipleTracksMultipleReleasesMultiClusters); } } catch (std::exception& e) { std::cerr << "Caught exception: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }