Switched to gtest for unit testing
This commit is contained in:
@@ -19,6 +19,7 @@ addons:
|
|||||||
- libtag1-dev
|
- libtag1-dev
|
||||||
- libpam0g-dev
|
- libpam0g-dev
|
||||||
- libgraphicsmagick++1-dev
|
- libgraphicsmagick++1-dev
|
||||||
|
- libgtest-dev
|
||||||
compiler:
|
compiler:
|
||||||
- clang
|
- clang
|
||||||
- gcc
|
- gcc
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ find_package(PkgConfig REQUIRED)
|
|||||||
pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib)
|
pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib)
|
||||||
pkg_check_modules(Config++ REQUIRED IMPORTED_TARGET libconfig++)
|
pkg_check_modules(Config++ REQUIRED IMPORTED_TARGET libconfig++)
|
||||||
pkg_check_modules(GraphicsMagick++ IMPORTED_TARGET GraphicsMagick++)
|
pkg_check_modules(GraphicsMagick++ IMPORTED_TARGET GraphicsMagick++)
|
||||||
|
find_package(GTest REQUIRED)
|
||||||
|
|
||||||
# WT
|
# WT
|
||||||
if (NOT Wt_FOUND)
|
if (NOT Wt_FOUND)
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ __Notes__:
|
|||||||
* a C++17 compiler is needed
|
* a C++17 compiler is needed
|
||||||
* ffmpeg version 4 minimum is required
|
* ffmpeg version 4 minimum is required
|
||||||
```sh
|
```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__:
|
__Notes__:
|
||||||
* libpam0g-dev is optional (only for using PAM authentication)
|
* libpam0g-dev is optional (only for using PAM authentication)
|
||||||
|
|||||||
+2
-1
@@ -36,7 +36,8 @@ ARG BUILD_PACKAGES=" \
|
|||||||
openssl-dev \
|
openssl-dev \
|
||||||
boost-dev \
|
boost-dev \
|
||||||
libconfig-dev \
|
libconfig-dev \
|
||||||
taglib-dev"
|
taglib-dev \
|
||||||
|
gtest-dev"
|
||||||
|
|
||||||
RUN apk add --no-cache --update ${BUILD_PACKAGES}
|
RUN apk add --no-cache --update ${BUILD_PACKAGES}
|
||||||
|
|
||||||
|
|||||||
@@ -103,13 +103,13 @@ createQuery(Session& session,
|
|||||||
for (std::string_view keyword : keywords)
|
for (std::string_view keyword : keywords)
|
||||||
{
|
{
|
||||||
clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||||
query.bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
query.bind("%" + escapeLikeKeyword(keyword) + "%");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::string_view keyword : keywords)
|
for (std::string_view keyword : keywords)
|
||||||
{
|
{
|
||||||
sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
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 ") + ")");
|
query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")");
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ createQuery(Session& session,
|
|||||||
query.join("track t ON t.release_id = r.id");
|
query.join("track t ON t.release_id = r.id");
|
||||||
|
|
||||||
for (std::string_view keyword : keywords)
|
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())
|
if (!clusterIds.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ createQuery(Session& session,
|
|||||||
auto query {session.getDboSession().query<T>(queryStr)};
|
auto query {session.getDboSession().query<T>(queryStr)};
|
||||||
|
|
||||||
for (std::string_view keyword : keywords)
|
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())
|
if (!clusterIds.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Database
|
|||||||
std::string
|
std::string
|
||||||
escapeLikeKeyword(std::string_view keyword)
|
escapeLikeKeyword(std::string_view keyword)
|
||||||
{
|
{
|
||||||
return StringUtils::escapeString(keyword, "*_", escapeChar);
|
return StringUtils::escapeString(keyword, "%_", escapeChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Database
|
} // namespace Database
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
|
|
||||||
add_executable(test-database
|
add_executable(test-database
|
||||||
|
Artist.cpp
|
||||||
|
Cluster.cpp
|
||||||
DatabaseTest.cpp
|
DatabaseTest.cpp
|
||||||
|
Track.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test-database PRIVATE
|
target_link_libraries(test-database PRIVATE
|
||||||
lmsdatabase
|
lmsdatabase
|
||||||
|
GTest::GTest
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test(NAME database COMMAND test-database)
|
gtest_discover_tests(test-database)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#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 <typename T>
|
||||||
|
class ScopedEntity
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
ScopedEntity(Database::Session& session, Args&& ...args)
|
||||||
|
: _session {session}
|
||||||
|
{
|
||||||
|
auto transaction {_session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
auto entity {T::create(_session, std::forward<Args>(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<Database::Artist>;
|
||||||
|
using ScopedCluster = ScopedEntity<Database::Cluster>;
|
||||||
|
using ScopedClusterType = ScopedEntity<Database::ClusterType>;
|
||||||
|
using ScopedRelease = ScopedEntity<Database::Release>;
|
||||||
|
using ScopedTrack = ScopedEntity<Database::Track>;
|
||||||
|
using ScopedTrackBookmark = ScopedEntity<Database::TrackBookmark>;
|
||||||
|
using ScopedTrackList = ScopedEntity<Database::TrackList>;
|
||||||
|
using ScopedUser = ScopedEntity<Database::User>;
|
||||||
|
|
||||||
|
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<TmpDatabase>();
|
||||||
|
{
|
||||||
|
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<TmpDatabase> _tmpDb {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
Database::Session session {_tmpDb->getDb()};
|
||||||
|
};
|
||||||
|
|
||||||
+218
-1737
File diff suppressed because it is too large
Load Diff
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Common.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
add_executable(test-som
|
add_executable(test-som
|
||||||
SomTest.cpp
|
SomTest.cpp
|
||||||
@@ -5,7 +6,8 @@ add_executable(test-som
|
|||||||
|
|
||||||
target_link_libraries(test-som PRIVATE
|
target_link_libraries(test-som PRIVATE
|
||||||
lmssom
|
lmssom
|
||||||
|
GTest::GTest
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test(NAME som COMMAND test-som)
|
gtest_discover_tests(test-som)
|
||||||
|
|
||||||
|
|||||||
+85
-67
@@ -17,25 +17,40 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <sstream>
|
#include <gtest/gtest.h>
|
||||||
#include <cassert>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "som/DataNormalizer.hpp"
|
#include "som/DataNormalizer.hpp"
|
||||||
#include "som/Network.hpp"
|
#include "som/Network.hpp"
|
||||||
|
|
||||||
using namespace SOM;
|
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<int> testMatrix {2, 2, 123};
|
Matrix<int> testMatrix {2, 2, 123};
|
||||||
assert((testMatrix[{0,0}] == 123));
|
{
|
||||||
assert((testMatrix[{0,1}] == 123));
|
const Position pos {0, 0};
|
||||||
assert((testMatrix[{1,0}] == 123));
|
EXPECT_EQ(testMatrix[pos], 123);
|
||||||
assert((testMatrix[{1,1}] == 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};
|
InputVector test1 {2};
|
||||||
@@ -48,69 +63,72 @@ int main()
|
|||||||
|
|
||||||
InputVector test3 {test1};
|
InputVector test3 {test1};
|
||||||
test3 += test2;
|
test3 += test2;
|
||||||
assert(std::abs(test3[0] - 1) < EPSILON);
|
EXPECT_LT(std::abs(test3[0] - 1), EPSILON);
|
||||||
assert(std::abs(test3[1] - 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<InputVector> 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<Position> positions;
|
||||||
|
for (const InputVector& data : trainData)
|
||||||
|
positions.insert(network.getClosestRefVectorPosition(data));
|
||||||
|
|
||||||
|
EXPECT_EQ(positions.size(), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Network network {2, 2, 1};
|
Position pos {network.getClosestRefVectorPosition(InputVector{1, 0.66})};
|
||||||
|
for (std::size_t i {}; i < 40; ++i)
|
||||||
const InputVector weights {1, 1};
|
|
||||||
std::vector<InputVector> 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));
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unordered_set<Position> positions;
|
InputVector input {1, 130 + static_cast<InputVector::value_type>(i) };
|
||||||
for (const InputVector& data : trainData)
|
normalizer.normalizeData(input);
|
||||||
positions.insert(network.getClosestRefVectorPosition(data));
|
|
||||||
assert(positions.size() == 4);
|
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<InputVector::value_type>(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<InputVector::value_type>(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<InputVector::value_type>(i) };
|
||||||
|
normalizer.normalizeData(input);
|
||||||
|
|
||||||
|
EXPECT_EQ(network.getClosestRefVectorPosition(input), pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
add_executable(test-utils
|
add_executable(test-utils
|
||||||
UtilsTest.cpp
|
String.cpp
|
||||||
|
RecursiveSharedMutex.cpp
|
||||||
|
Utils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test-utils PRIVATE
|
target_link_libraries(test-utils PRIVATE
|
||||||
lmsutils
|
lmsutils
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
|
GTest::GTest
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test(NAME utils COMMAND test-utils)
|
gtest_discover_tests(test-utils)
|
||||||
|
|
||||||
|
|||||||
@@ -18,85 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cassert>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <stdlib.h>
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "utils/RecursiveSharedMutex.hpp"
|
#include "utils/RecursiveSharedMutex.hpp"
|
||||||
#include "utils/String.hpp"
|
|
||||||
|
|
||||||
|
TEST(RecursiveSharedMutex, SingleThreaded)
|
||||||
void
|
|
||||||
testStrings()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
const std::string test{"a"};
|
|
||||||
|
|
||||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, "")};
|
|
||||||
assert(strings.size() == 1);
|
|
||||||
assert(strings.front() == "a");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const std::string test{"a b"};
|
|
||||||
|
|
||||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, "|")};
|
|
||||||
assert(strings.size() == 1);
|
|
||||||
assert(strings.front() == "a b");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const std::string test{" a"};
|
|
||||||
|
|
||||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
|
||||||
assert(strings.size() == 1);
|
|
||||||
assert(strings.front() == "a");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const std::string test{"a "};
|
|
||||||
|
|
||||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
|
||||||
assert(strings.size() == 1);
|
|
||||||
assert(strings.front() == "a");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const std::string test{"a b"};
|
|
||||||
|
|
||||||
const std::vector<std::string_view> 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<std::string_view> 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()
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
RecursiveSharedMutex mutex;
|
RecursiveSharedMutex mutex;
|
||||||
@@ -124,7 +54,10 @@ testSharedMutex()
|
|||||||
std::shared_lock lock2 {mutex};
|
std::shared_lock lock2 {mutex};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RecursiveSharedMutex, MultiThreaded)
|
||||||
|
{
|
||||||
{
|
{
|
||||||
constexpr std::size_t nbThreads {10};
|
constexpr std::size_t nbThreads {10};
|
||||||
std::vector<std::thread> threads;
|
std::vector<std::thread> threads;
|
||||||
@@ -171,20 +104,3 @@ testSharedMutex()
|
|||||||
t.join();
|
t.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
testStrings();
|
|
||||||
testSharedMutex();
|
|
||||||
}
|
|
||||||
catch (std::exception& e)
|
|
||||||
{
|
|
||||||
std::cerr << "Caught exception: " << e.what();
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
|
TEST(StringUtils, splitString)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const std::string test{"a"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, "")};
|
||||||
|
ASSERT_EQ(strings.size(), 1);
|
||||||
|
EXPECT_EQ(strings.front() , "a");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{"a b"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, "|")};
|
||||||
|
ASSERT_EQ(strings.size(), 1);
|
||||||
|
EXPECT_EQ(strings.front(), "a b");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{" a"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
||||||
|
ASSERT_EQ(strings.size(), 1);
|
||||||
|
EXPECT_EQ(strings.front(), "a");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{"a "};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
||||||
|
ASSERT_EQ(strings.size(), 1);
|
||||||
|
EXPECT_EQ(strings.front(), "a");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string test{"a b"};
|
||||||
|
|
||||||
|
const std::vector<std::string_view> 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<std::string_view> 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("**||", "*|", '_'), "_*_*_|_|");
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user