Switched to gtest for unit testing
This commit is contained in:
@@ -19,6 +19,7 @@ addons:
|
||||
- libtag1-dev
|
||||
- libpam0g-dev
|
||||
- libgraphicsmagick++1-dev
|
||||
- libgtest-dev
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+2
-1
@@ -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}
|
||||
|
||||
|
||||
@@ -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 ") + ")");
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ createQuery(Session& session,
|
||||
auto query {session.getDboSession().query<T>(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())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
+85
-67
@@ -17,25 +17,40 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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<int> 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<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};
|
||||
|
||||
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));
|
||||
|
||||
Position pos {network.getClosestRefVectorPosition(InputVector{1, 0.66})};
|
||||
for (std::size_t i {}; i < 40; ++i)
|
||||
{
|
||||
std::unordered_set<Position> positions;
|
||||
for (const InputVector& data : trainData)
|
||||
positions.insert(network.getClosestRefVectorPosition(data));
|
||||
assert(positions.size() == 4);
|
||||
InputVector input {1, 130 + static_cast<InputVector::value_type>(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<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
|
||||
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)
|
||||
|
||||
|
||||
@@ -18,85 +18,15 @@
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "utils/RecursiveSharedMutex.hpp"
|
||||
#include "utils/String.hpp"
|
||||
|
||||
|
||||
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()
|
||||
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<std::thread> 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;
|
||||
}
|
||||
@@ -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