Switched to gtest for unit testing

This commit is contained in:
emeric
2021-05-03 13:20:43 +02:00
parent 444d4af3c6
commit 67ce7151ff
20 changed files with 2131 additions and 1905 deletions
+1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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}
+2 -2
View File
@@ -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 ") + ")");
+1 -1
View File
@@ -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())
{ {
+1 -1
View File
@@ -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())
{ {
+1 -1
View File
@@ -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
+314
View File
@@ -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());
}
}
+5 -1
View File
@@ -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
+182
View File
@@ -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()};
};
File diff suppressed because it is too large Load Diff
+90
View File
@@ -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());
}
}
}
+3 -1
View File
@@ -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
View File
@@ -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();
} }
+6 -2
View File
@@ -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;
}
+89
View File
@@ -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("**||", "*|", '_'), "_*_*_|_|");
}
+27
View File
@@ -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();
}