Add test project for playlist library, implemented tests for existing code and fixed a few issues found by testing.

This commit is contained in:
Kai Blaschke
2022-10-27 12:11:28 +02:00
parent ebce27fbfb
commit 4dfae6bf22
13 changed files with 564 additions and 14 deletions

View File

@ -9,6 +9,11 @@ add_library(projectM_playlist STATIC
playlist.h
)
target_include_directories(projectM_playlist
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
target_link_libraries(projectM_playlist
PUBLIC
projectM::API

View File

@ -7,16 +7,17 @@ namespace Playlist {
uint32_t Playlist::Size() const
{
return 0;
return m_items.size();
}
void Playlist::Clear()
{
m_items.clear();
}
const std::vector<Item>& Playlist::Items()
const std::vector<Item>& Playlist::Items() const
{
return m_items;
}
@ -24,6 +25,11 @@ const std::vector<Item>& Playlist::Items()
bool Playlist::AddItem(const std::string& filename, uint32_t index, bool allowDuplicates)
{
if (filename.empty())
{
return false;
}
if (!allowDuplicates)
{
if (std::find(m_items.begin(), m_items.end(), filename) != m_items.end())
@ -63,5 +69,11 @@ void Playlist::Shuffle(bool enabled)
m_shuffle = enabled;
}
auto Playlist::Shuffle() -> bool
{
return m_shuffle;
}
} // namespace Playlist
} // namespace ProjectM

View File

@ -3,6 +3,7 @@
#include "Item.hpp"
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
@ -18,25 +19,38 @@ namespace Playlist {
class Playlist
{
public:
/**
* Short-hand constant which can be used in AddItem() to add new presets at the end of the playlist.
*/
static constexpr auto InsertAtEnd = std::numeric_limits<uint32_t>::max();
/**
* Destructor.
*/
virtual ~Playlist() = default;
/**
* @brief Returns the number of items in the current playlist.
* @return The number of items in the current playlist.
*/
uint32_t Size() const;
virtual uint32_t Size() const;
/**
* @brief Clears the current playlist.
*/
void Clear();
virtual void Clear();
/**
* @brief Returns the playlist items.
* @return A vector with items in the current playlist.
*/
const std::vector<Item>& Items();
virtual const std::vector<Item>& Items() const;
/**
* @brief Adds a preset file to the playlist.
*
* Use Playlist::InsertAtEnd as index to always insert an item at the end of the playlist.
*
* @param filename The file path and name to add.
* @param index The index to insert the preset at. If larger than the playlist size, it's added
* to the end of the playlist.
@ -44,7 +58,7 @@ public:
* (including the path) are not added if already present.
* @return True if the preset was added, false if it already existed.
*/
auto AddItem(const std::string& filename, uint32_t index, bool allowDuplicates) -> bool;
virtual auto AddItem(const std::string& filename, uint32_t index, bool allowDuplicates) -> bool;
/**
* @brief Removed a playlist item at the given playlist index.
@ -52,13 +66,19 @@ public:
* @return True if an item was removed, false if the index was out of bounds and no item was
* removed..
*/
auto RemoveItem(uint32_t index) -> bool;
virtual auto RemoveItem(uint32_t index) -> bool;
/**
* @brief Enables or disabled shuffle mode.
* @param enabled True to enable shuffle mode, false to disable.
*/
void Shuffle(bool enabled);
virtual void Shuffle(bool enabled);
/**
* @brief Returns the enable state of shuffle mode.
* @return True if shuffle is enabled, false if not.
*/
virtual auto Shuffle() -> bool;
private:
std::vector<Item> m_items; //!< Items in the current playlist.

View File

@ -2,6 +2,8 @@
#include <limits>
using ProjectM::Playlist::Playlist;
PlaylistCWrapper::PlaylistCWrapper(projectm_handle projectMInstance)
{
}
@ -19,12 +21,13 @@ auto playlist_handle_to_instance(projectm_playlist_handle instance) -> PlaylistC
}
void projectm_playlist_free_string_array(const char** array)
void projectm_playlist_free_string_array(char** array)
{
int index{0};
while (array[index] != nullptr)
{
delete[] array[index];
index++;
}
delete[] array;
}
@ -87,6 +90,7 @@ auto projectm_playlist_items(projectm_playlist_handle instance) -> char**
auto filename = item.Filename();
array[index] = new char[filename.length() + 1]{};
filename.copy(array[index], filename.length());
index++;
}
return array;
@ -107,7 +111,7 @@ auto projectm_playlist_add_preset(projectm_playlist_handle instance, const char*
{
auto* playlist = playlist_handle_to_instance(instance);
return playlist->AddItem(filename, std::numeric_limits<uint32_t>::max(), allow_duplicates);
return playlist->AddItem(filename, Playlist::InsertAtEnd, allow_duplicates);
}
@ -139,7 +143,7 @@ uint32_t projectm_playlist_add_presets(projectm_playlist_handle instance, const
continue;
}
if (playlist->AddItem(filenames[index], std::numeric_limits<uint32_t>::max(), allow_duplicates))
if (playlist->AddItem(filenames[index], Playlist::InsertAtEnd, allow_duplicates))
{
addCount++;
}
@ -168,7 +172,7 @@ auto projectm_playlist_insert_presets(projectm_playlist_handle instance, const c
continue;
}
if (playlist->AddItem(filenames[filenameIndex], index + filenameIndex, allow_duplicates))
if (playlist->AddItem(filenames[filenameIndex], index + addCount, allow_duplicates))
{
addCount++;
}

View File

@ -17,7 +17,7 @@ public:
* @brief Reconnects the playlist instance to another projectM instance, or disconnects it.
* @param projectMInstance A pointer to an existing projectM instance or nullptr to disconnect.
*/
void Connect(projectm_handle projectMInstance);
virtual void Connect(projectm_handle projectMInstance);
private:
projectm_handle m_projectMInstance{nullptr}; //!< The projectM instance handle this instance is connected to.

View File

@ -17,7 +17,7 @@ typedef struct projectm_playlist* projectm_playlist_handle; //!< A pointer to th
*
* @param array The pointer to the array of strings that should be freed.
*/
void projectm_playlist_free_string_array(const char** array);
void projectm_playlist_free_string_array(char** array);
/**
* @brief Creates a playlist manager for the given projectM instance

View File

@ -1 +1,2 @@
add_subdirectory(libprojectM)
add_subdirectory(playlist)

207
tests/playlist/APITest.cpp Normal file
View File

@ -0,0 +1,207 @@
#include "PlaylistCWrapperMock.h"
#include <playlist.h>
#include <gtest/gtest.h>
using ::testing::Return;
using ::testing::ReturnRef;
/**
* This suite only tests the API forwarding to the wrapper, not the actual playlist functionality!
*
* Also note that the playlist wrapper class should never be instantiated directly in production
* code. ALWAYS use projectm_playlist_create() to create the instance and only use the C API
* functions to access the functionality. The extreme use of reinterpret_cast<>() in this test suite
* should make that quite obvious.
*/
TEST(projectMPlaylist, APICreate)
{
auto* playlistHandle = projectm_playlist_create(nullptr);
ASSERT_NE(playlistHandle, nullptr);
projectm_playlist_destroy(playlistHandle);
}
TEST(projectMPlaylist, APIConnect)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, Connect(nullptr))
.Times(1);
projectm_playlist_connect(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), nullptr);
projectm_handle someHandle{reinterpret_cast<projectm_handle>(2537)};
EXPECT_CALL(mockPlaylist, Connect(someHandle)).Times(1);
projectm_playlist_connect(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), someHandle);
}
TEST(projectMPlaylist, APISize)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, Size())
.Times(1)
.WillOnce(Return(2336));
ASSERT_EQ(projectm_playlist_size(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist)), 2336);
}
TEST(projectMPlaylist, APIClear)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, Clear())
.Times(1);
projectm_playlist_clear(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist));
}
TEST(projectMPlaylist, APIItems)
{
PlaylistCWrapperMock mockPlaylist;
std::vector<ProjectM::Playlist::Item> items{
ProjectM::Playlist::Item("/some/file"),
ProjectM::Playlist::Item("/another/file")};
EXPECT_CALL(mockPlaylist, Items())
.Times(1)
.WillOnce(ReturnRef(items));
auto* returnedItems = projectm_playlist_items(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist));
ASSERT_NE(returnedItems, nullptr);
ASSERT_NE(*returnedItems, nullptr);
EXPECT_STREQ(*returnedItems, items.at(0).Filename().c_str());
ASSERT_NE(*(returnedItems + 1), nullptr);
EXPECT_STREQ(*(returnedItems + 1), items.at(1).Filename().c_str());
EXPECT_EQ(*(returnedItems + 2), nullptr);
projectm_playlist_free_string_array(returnedItems);
}
TEST(projectMPlaylist, APIAddPath)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_EQ(projectm_playlist_add_path(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), "", true, false), 0);
}
TEST(projectMPlaylist, APIAddPreset)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, AddItem("/some/file", ProjectM::Playlist::Playlist::InsertAtEnd, false))
.Times(1)
.WillOnce(Return(true));
EXPECT_TRUE(projectm_playlist_add_preset(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), "/some/file", false));
}
TEST(projectMPlaylist, APIInsertPreset)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, AddItem("/some/file", 34, true))
.Times(1)
.WillOnce(Return(true));
EXPECT_TRUE(projectm_playlist_insert_preset(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), "/some/file", 34, true));
}
TEST(projectMPlaylist, APIAddPresets)
{
PlaylistCWrapperMock mockPlaylist;
const char firstFile[]{"/some/file"};
const char secondFile[]{"/another/file"};
const char thirdFile[]{"/another/file"};
const char* presetList[]{firstFile, secondFile, thirdFile};
EXPECT_CALL(mockPlaylist, AddItem("/some/file", ProjectM::Playlist::Playlist::InsertAtEnd, false))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(mockPlaylist, AddItem("/another/file", ProjectM::Playlist::Playlist::InsertAtEnd, false))
.Times(2)
.WillOnce(Return(true))
.WillOnce(Return(false));
EXPECT_EQ(projectm_playlist_add_presets(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), presetList, 3, false), 2);
}
TEST(projectMPlaylist, APIInsertPresets)
{
PlaylistCWrapperMock mockPlaylist;
const char firstFile[]{"/some/file"};
const char secondFile[]{"/some/file"};
const char thirdFile[]{"/another/file"};
const char* presetList[]{firstFile, secondFile, thirdFile};
EXPECT_CALL(mockPlaylist, AddItem("/some/file", 34, false))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(mockPlaylist, AddItem("/some/file", 35, false))
.Times(1)
.WillOnce(Return(false));
EXPECT_CALL(mockPlaylist, AddItem("/another/file", 35, false)) // Index not incremented!
.Times(1)
.WillOnce(Return(true));
EXPECT_EQ(projectm_playlist_insert_presets(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), presetList, 3, 34, false), 2);
}
TEST(projectMPlaylist, APIRemovePreset)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, RemoveItem(0))
.Times(2)
.WillOnce(Return(true))
.WillOnce(Return(false));
EXPECT_TRUE(projectm_playlist_remove_preset(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), 0));
EXPECT_FALSE(projectm_playlist_remove_preset(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), 0));
}
TEST(projectMPlaylist, APIRemovePresets)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, RemoveItem(0))
.Times(3)
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(false));
EXPECT_EQ(projectm_playlist_remove_presets(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), 0, 3), 2);
}
TEST(projectMPlaylist, APISetShuffle)
{
PlaylistCWrapperMock mockPlaylist;
EXPECT_CALL(mockPlaylist, Shuffle(true))
.Times(1);
EXPECT_CALL(mockPlaylist, Shuffle(false))
.Times(1);
projectm_playlist_set_shuffle(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), true);
projectm_playlist_set_shuffle(reinterpret_cast<projectm_playlist_handle>(&mockPlaylist), false);
}

View File

@ -0,0 +1,23 @@
if(NOT TARGET projectM_playlist)
return()
endif()
find_package(GTest 1.10 REQUIRED NO_MODULE)
add_executable(projectM-playlist-unittest
APITest.cpp
ItemTest.cpp
PlaylistCWrapperMock.cpp
PlaylistCWrapperMock.h
PlaylistTest.cpp
)
target_link_libraries(projectM-playlist-unittest
PRIVATE
projectM_playlist
GTest::gmock
GTest::gtest
GTest::gtest_main
)
add_test(NAME projectM-playlist-unittest COMMAND projectM-playlist-unittest)

View File

@ -0,0 +1,25 @@
#include <Item.hpp>
#include <gtest/gtest.h>
TEST(projectMPlaylist, ItemCreate)
{
ASSERT_NO_THROW(ProjectM::Playlist::Item item("/some/file"));
}
TEST(projectMPlaylist, ItemGetFilename)
{
ProjectM::Playlist::Item item("/some/file");
ASSERT_EQ(item.Filename(), "/some/file");
}
TEST(projectMPlaylist, ItemFilenameEquality)
{
ProjectM::Playlist::Item item("/some/file");
EXPECT_TRUE(item == "/some/file");
EXPECT_FALSE(item == "/some/other/file");
}

View File

@ -0,0 +1 @@
#include "PlaylistCWrapperMock.h"

View File

@ -0,0 +1,22 @@
#pragma once
#include <PlaylistCWrapper.h>
#include <gmock/gmock.h>
class PlaylistCWrapperMock : public PlaylistCWrapper
{
public:
PlaylistCWrapperMock() : PlaylistCWrapper(nullptr) {};
// PlaylistCWrapper members
MOCK_METHOD(void, Connect, (projectm_handle));
// Playlist members
MOCK_METHOD(uint32_t, Size, (), (const));
MOCK_METHOD(void, Clear, ());
MOCK_METHOD(const std::vector<ProjectM::Playlist::Item>&, Items, (), (const));
MOCK_METHOD(bool, AddItem, (const std::string&, uint32_t, bool));
MOCK_METHOD(bool, RemoveItem, (uint32_t));
MOCK_METHOD(void, Shuffle, (bool));
};

View File

@ -0,0 +1,230 @@
#include <Playlist.hpp>
#include <gtest/gtest.h>
using ProjectM::Playlist::Playlist;
TEST(projectMPlaylist, PlaylistCreate)
{
ASSERT_NO_THROW(Playlist playlist);
}
TEST(projectMPlaylist, PlaylistSize)
{
Playlist playlist;
EXPECT_EQ(playlist.Size(), 0);
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_EQ(playlist.Size(), 1);
}
TEST(projectMPlaylist, PlaylistClear)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_EQ(playlist.Size(), 1);
playlist.Clear();
EXPECT_EQ(playlist.Size(), 0);
}
TEST(projectMPlaylist, PlaylistItems)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 2);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 2);
EXPECT_EQ(items.at(0).Filename(), "/some/file");
EXPECT_EQ(items.at(1).Filename(), "/some/other/file");
}
TEST(projectMPlaylist, PlaylistAddItemEmptyFilename)
{
Playlist playlist;
EXPECT_FALSE(playlist.AddItem("", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 0);
}
TEST(projectMPlaylist, PlaylistAddItemAtEnd)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/yet/another/file", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 3);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 3);
EXPECT_EQ(items.at(2).Filename(), "/yet/another/file");
}
TEST(projectMPlaylist, PlaylistAddItemAtFront)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", 0, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", 0, false));
EXPECT_TRUE(playlist.AddItem("/yet/another/file", 0, false));
ASSERT_EQ(playlist.Size(), 3);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 3);
EXPECT_EQ(items.at(0).Filename(), "/yet/another/file");
EXPECT_EQ(items.at(1).Filename(), "/some/other/file");
EXPECT_EQ(items.at(2).Filename(), "/some/file");
}
TEST(projectMPlaylist, PlaylistAddItemInMiddle)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", 0, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/yet/another/file", 1, false));
ASSERT_EQ(playlist.Size(), 3);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 3);
EXPECT_EQ(items.at(0).Filename(), "/some/file");
EXPECT_EQ(items.at(1).Filename(), "/yet/another/file");
EXPECT_EQ(items.at(2).Filename(), "/some/other/file");
}
TEST(projectMPlaylist, PlaylistAddItemDuplicates)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, true));
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, true));
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, true));
ASSERT_EQ(playlist.Size(), 3);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 3);
EXPECT_EQ(items.at(2).Filename(), "/some/file");
}
TEST(projectMPlaylist, PlaylistAddItemNoDuplicates)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_FALSE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_FALSE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 1);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 1);
EXPECT_EQ(items.at(0).Filename(), "/some/file");
}
TEST(projectMPlaylist, PlaylistRemoveItemFromEnd)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/yet/another/file", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 3);
EXPECT_TRUE(playlist.RemoveItem(2));
ASSERT_EQ(playlist.Size(), 2);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 2);
EXPECT_EQ(items.at(0).Filename(), "/some/file");
EXPECT_EQ(items.at(1).Filename(), "/some/other/file");
}
TEST(projectMPlaylist, PlaylistRemoveItemFromFront)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/yet/another/file", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 3);
EXPECT_TRUE(playlist.RemoveItem(0));
ASSERT_EQ(playlist.Size(), 2);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 2);
EXPECT_EQ(items.at(0).Filename(), "/some/other/file");
EXPECT_EQ(items.at(1).Filename(), "/yet/another/file");
}
TEST(projectMPlaylist, PlaylistRemoveItemFromMiddle)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/yet/another/file", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 3);
EXPECT_TRUE(playlist.RemoveItem(1));
ASSERT_EQ(playlist.Size(), 2);
const auto& items = playlist.Items();
ASSERT_EQ(items.size(), 2);
EXPECT_EQ(items.at(0).Filename(), "/some/file");
EXPECT_EQ(items.at(1).Filename(), "/yet/another/file");
}
TEST(projectMPlaylist, PlaylistRemoveItemIndexOutOfBounds)
{
Playlist playlist;
EXPECT_TRUE(playlist.AddItem("/some/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/some/other/file", Playlist::InsertAtEnd, false));
EXPECT_TRUE(playlist.AddItem("/yet/another/file", Playlist::InsertAtEnd, false));
ASSERT_EQ(playlist.Size(), 3);
EXPECT_FALSE(playlist.RemoveItem(100));
ASSERT_EQ(playlist.Size(), 3);
}
TEST(projectMPlaylist, PlaylistShuffleEnableDisable)
{
Playlist playlist;
EXPECT_FALSE(playlist.Shuffle());
playlist.Shuffle(true);
EXPECT_TRUE(playlist.Shuffle());
playlist.Shuffle(false);
EXPECT_FALSE(playlist.Shuffle());
}