Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Enhance custom string- and char-type support #65

Merged
merged 24 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
74101ca
fix: string concepts expects its char-type to be regular
DNKpp Oct 21, 2024
f832311
fix: declare ``is_character`` as user-customizable
DNKpp Oct 21, 2024
805a810
feat: add default definition for detail::character_literal_printer
DNKpp Oct 21, 2024
96ac2aa
refactor: use intermediate type with same size for char printing
DNKpp Oct 21, 2024
855b7a8
refactor: simplify string printing
DNKpp Oct 21, 2024
c01a531
feat: add native print support for strings of printable custom char-t…
DNKpp Oct 21, 2024
e1a2d89
feat: add native print support for strings of formattbale char-types
DNKpp Oct 21, 2024
070494a
feat: add CustomString examples
DNKpp Oct 21, 2024
a654928
docs: enhance string documentation
DNKpp Oct 21, 2024
f5f32f6
fix: add missing ns prefix for type specializations
DNKpp Oct 21, 2024
0831721
fix: remove invalid constexpr
DNKpp Oct 21, 2024
c039fc5
fix: apply maybe_unused attribute on matches::_
DNKpp Oct 21, 2024
078d923
refactor: make intermediate_char a comon type-trait uint_with_size
DNKpp Oct 22, 2024
c6829c7
fix: mark print functional as maybe_unused
DNKpp Oct 22, 2024
af40691
fix: add missing cstdint include in TypeTraits.hpp
DNKpp Oct 22, 2024
6b28b93
refactor: move StringViewT definition into Fwd.hpp
DNKpp Oct 22, 2024
a611c41
refactor: rework detail::character_literal_printer to customizable st…
DNKpp Oct 22, 2024
e9453f2
docs: add note for string_literal_prefix customization
DNKpp Oct 22, 2024
9b5afa4
docs: add custom string section to README.md
DNKpp Oct 22, 2024
d380eed
fix: be more strict by formattable char test, to workaround issues on…
DNKpp Oct 22, 2024
b42e523
chore: add .idea to gitignore
DNKpp Oct 22, 2024
6e280e5
fix: correct typo
DNKpp Oct 22, 2024
271ddc8
fix: check for emptiness instead of ""
DNKpp Oct 22, 2024
87e0ad1
fix: workaround ostringstream issue on clang-16 with libc++
DNKpp Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
out
.vs
.idea
*.user
CMakeSettings.json
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ The expectation policy has full control, whether a match can be made or shall be
(like returning a value or throwing an exception). They can implement arbitrary logic, so feel free to experiment. There is no base type requirement,
they simply have to satisfy either the ``mimicpp::expectation_policy_for``, ``mimicpp::control_policy`` or ``mimicpp::finalize_policy_for``.

### Bring your own string- and char-types

If you are working with a huge framework, chances are good, that this framework utilizes a custom string- or even char-type (like ``QChar`` and ``QString`` from Qt).
They may look different, but in fact they are just strings, so it would be nice to make them fully compatible with the existing string-matchers.
``mimic++`` supports that, users just have to provide some trait-specializations. For more infos, have a loook into the string section of the doxygen documentation.

---

## Documentation
Expand Down
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ find_package(Catch2 REQUIRED)
add_executable(
mimicpp-examples
"CustomPrinter.cpp"
"CustomString.cpp"
"Finalizers.cpp"
"InterfaceMock.cpp"
"Mock.cpp"
Expand Down
200 changes: 200 additions & 0 deletions examples/CustomString.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// // Copyright Dominic (DNKpp) Koepke 2024 - 2024.
// // Distributed under the Boost Software License, Version 1.0.
// // (See accompanying file LICENSE_1_0.txt or copy at
// // https://www.boost.org/LICENSE_1_0.txt)

#include "mimic++/String.hpp"

#include <catch2/catch_test_macros.hpp>

#include "mimic++/Mock.hpp"

namespace
{
//! [MyString]
class MyString
{
public:
explicit MyString(std::string str) noexcept
: m_Inner{std::move(str)}
{
}

std::string_view view() const noexcept
{
return std::string_view{m_Inner};
}

private:
std::string m_Inner{};
};

//! [MyString]
}

//! [MyString trait]
template <>
struct mimicpp::string_traits<MyString>
{
// must be the underlying char type
using char_t = char;

// must be a std::ranges::view like type and must be contiguous
using view_t = std::string_view;

// must construct a view object
[[nodiscard]]
static view_t view(const MyString& str) noexcept
{
return str.view();
}
};

//! [MyString trait]

TEST_CASE(
"mimic++ supports custom strings.",
"[example][example::string]"
)
{
//! [MyString example]
STATIC_REQUIRE(mimicpp::string<MyString>);

namespace expect = mimicpp::expect;
namespace matches = mimicpp::matches;
using matches::_;

mimicpp::Mock<void(MyString)> mock{};
SCOPED_EXP mock.expect_call("Hello, World!");
SCOPED_EXP mock.expect_call(matches::str::starts_with("Hi", mimicpp::case_insensitive));

mock(MyString{"Hello, World!"}); // matches the first expectation
mock(MyString{"hI, mimic++"}); // matches the second expectation (case-insensitive).
//! [MyString example]
}

//! [custom_char]
struct my_char
{
char c{};

bool operator==(const my_char&) const = default;
};

//! [custom_char]

//! [custom_string]
class ComplexString
{
public:
explicit ComplexString(std::vector<my_char> str) noexcept
: m_Inner{std::move(str)}
{
}

[[nodiscard]]
auto begin() const noexcept
{
return m_Inner.cbegin();
}

[[nodiscard]]
auto end() const noexcept
{
return m_Inner.cend();
}

private:
std::vector<my_char> m_Inner{};
};

//! [custom_string]

//! [custom_char trait]
template <>
struct mimicpp::is_character<my_char>
: public std::true_type
{
};

//! [custom_char trait]

//! [custom_string traits]
template <>
struct mimicpp::string_traits<ComplexString>
{
// must be the underlying char type
using char_t = my_char;

// must be a std::ranges::view like type and must be contiguous
using view_t = std::span<const char_t>;

// must construct a view object
[[nodiscard]]
static view_t view(const ComplexString& str) noexcept
{
return std::span{
str.begin(),
str.end()
};
}
};

//! [custom_string traits]

TEST_CASE(
"mimic++ supports complex custom strings.",
"[example][example::string]"
)
{
//! [custom_string example]
STATIC_REQUIRE(mimicpp::string<ComplexString>);

mimicpp::Mock<void(ComplexString)> mock{};
ComplexString s{{{'A'}, {'B'}, {'C'}}};
SCOPED_EXP mock.expect_call(s);

mock(s);
//! [custom_string example]
}

//! [custom_char case-folding]
template <>
struct mimicpp::string_case_fold_converter<my_char>
{
// the string_case_fold_converter must expect the string's view-type and should return
// a forward-range with the underlying char-type.
[[nodiscard]]
auto operator ()(string_view_t<ComplexString> view) const
{
return view
| std::views::transform(
[](const my_char c)
{
// see notes of https://en.cppreference.com/w/cpp/string/byte/toupper
return my_char{
static_cast<char>(
static_cast<unsigned char>(std::toupper(c.c)))
};
});
}
};

//! [custom_char case-folding]

TEST_CASE(
"mimic++ supports case-folding for custom char-types.",
"[example][example::string]"
)
{
//! [custom_string case-insensitive example]
STATIC_REQUIRE(mimicpp::case_foldable_string<ComplexString>);

namespace matches = mimicpp::matches;

mimicpp::Mock<void(ComplexString)> mock{};
SCOPED_EXP mock.expect_call(matches::str::starts_with(ComplexString{{{'A'}, {'B'}, {'C'}}}, mimicpp::case_insensitive));

mock(ComplexString{{{'a'}, {'B'}, {'c'}}});
//! [custom_string case-insensitive example]
}
18 changes: 18 additions & 0 deletions include/mimic++/Fwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#pragma once

#include <string>
#include <string_view>

namespace mimicpp::call
{
Expand Down Expand Up @@ -144,6 +145,22 @@ namespace mimicpp
template <typename First, typename... Others>
inline constexpr bool is_overload_set_v = is_overload_set<First, Others...>::value;

/**
* \brief Primary template, purposely undefined.
* \ingroup TYPE_TRAITS_UINT_WITH_SIZE
* \tparam byteCount The expected size.
*/
template <std::size_t byteCount>
struct uint_with_size;

/**
* \brief Convenience constant, exposing the ``value`` member of the actual type-trait.
* \ingroup TYPE_TRAITS_UINT_WITH_SIZE
* \tparam byteCount The expected size.
*/
template <std::size_t byteCount>
using uint_with_size_t = typename uint_with_size<byteCount>::type;

template <typename T>
struct is_character;

Expand Down Expand Up @@ -188,6 +205,7 @@ namespace mimicpp
using CharT = char;
using CharTraitsT = std::char_traits<CharT>;
using StringT = std::basic_string<CharT, CharTraitsT>;
using StringViewT = std::basic_string_view<CharT, CharTraitsT>;
}

namespace mimicpp::sequence
Expand Down
2 changes: 1 addition & 1 deletion include/mimic++/Matcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ namespace mimicpp::matches
* \brief The wildcard matcher, always matching.
* \snippet Requirements.cpp matcher wildcard
*/
inline constexpr WildcardMatcher _{};
[[maybe_unused]] inline constexpr WildcardMatcher _{};

/**
* \brief Tests, whether the target compares equal to the expected value.
Expand Down
Loading
Loading