diff --git a/.cppcheck.suppress b/.cppcheck.suppress index ca019ff..33a1dfa 100644 --- a/.cppcheck.suppress +++ b/.cppcheck.suppress @@ -1,2 +1,5 @@ unusedFunction -duplInheritedMember \ No newline at end of file +duplInheritedMember +useStlAlgorithm +normalCheckLevelMaxBranches +knownConditionTrueFalse:test/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 63f4e75..e9fb01d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -117,4 +117,33 @@ "stack": "cpp", "ranges": "cpp" }, -} \ No newline at end of file + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#65c89b", + "activityBar.activeBorder": "#15202b", + "activityBar.background": "#65c89b", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#945bc4", + "activityBarBadge.foreground": "#e7e7e7", + "activityBarTop.activeBackground": "#65c89b", + "activityBarTop.activeBorder": "#15202b", + "activityBarTop.background": "#65c89b", + "activityBarTop.foreground": "#15202b", + "activityBarTop.inactiveForeground": "#15202b99", + "commandCenter.border": "#15202b99", + "commandCenter.foreground": "#15202b", + "sash.hoverBorder": "#65c89b", + "statusBar.background": "#42b883", + "statusBar.debuggingBackground": "#42b883", + "statusBar.debuggingForeground": "#15202b", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#359268", + "statusBarItem.remoteBackground": "#f0e8f7", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#42b883", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#42b88399", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.remoteColor": "#42b883", +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da8ea2..3018899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## Next Release +### Enum + +- split `enum.hpp` into `enum/enum.hpp`, `enum/enum_bit_flags.hpp` (renamed from `enum_bit_flage.hpp`) and `enum/enum_string.hpp`, with `mstd/enum.hpp` now re-exporting all three +- move `EnumFromString`/`EnumToString` into namespace `mstd`, fixing a compile error in `from_string` which referenced `mstd::EnumFromString` on an undefined name +- add extensive unit test coverage for `MSTD_ENUM` and `MSTD_ENUM_BITFLAG` + ## [0.3.0](https://github.com/repo/owner/releases/tag/0.3.0) - 2026-09-26 diff --git a/include/mstd/enum.hpp b/include/mstd/enum.hpp index ccf735e..bff8d87 100644 --- a/include/mstd/enum.hpp +++ b/include/mstd/enum.hpp @@ -23,176 +23,7 @@ #ifndef __MSTD__ENUM_HPP__ #define __MSTD__ENUM_HPP__ -#include // IWYU pragma: keep -#include // IWYU pragma: keep -#include // IWYU pragma: keep -#include // IWYU pragma: keep -#include // IWYU pragma: keep -#include // IWYU pragma: keep -#include // IWYU pragma: keep - -#include "mstd/type_traits/enum_traits.hpp" // IWYU pragma: keep -#include "mstd/type_traits/string.hpp" // IWYU pragma: keep - -// -// Element expanders -// -// X(Name) -> Name, -// X(Name, 42) -> Name = 42, -#define MSTD_ENUM_MAKE_ENUM(name, ...) name __VA_OPT__(= __VA_ARGS__), - -// X(Name) or X(Name, 42) -> EnumName::Name, -#define MSTD_ENUM_MAKE_VALUE(name, ...) name, - -// X(Name) -> "Name", -#define MSTD_ENUM_MAKE_STRING(name, ...) #name, - -// ------------------------------------------------------------ -// Main macro -// ------------------------------------------------------------ -#ifndef Q_MOC_RUN -#define MSTD_ENUM(EnumName, Underlying, LIST) \ - enum class EnumName : Underlying \ - { \ - LIST(MSTD_ENUM_MAKE_ENUM) \ - }; \ - \ - struct EnumName##Meta \ - { \ - using type = EnumName; \ - using underlying_type = Underlying; \ - using enum EnumName; \ - static constexpr std::string_view EnumNameStr = #EnumName; \ - \ - static constexpr auto values = \ - std::to_array({LIST(MSTD_ENUM_MAKE_VALUE)}); \ - \ - static constexpr std::span values_view() \ - { \ - return values; \ - } \ - \ - static constexpr auto names = \ - std::to_array({LIST(MSTD_ENUM_MAKE_STRING)}); \ - \ - static constexpr std::size_t size = values.size(); \ - \ - static constexpr auto begin() { return values.begin(); } \ - static constexpr auto end() { return values.end(); } \ - \ - static constexpr std::string_view name(EnumName enum_) \ - { \ - for (std::size_t i = 0; i < size; ++i) \ - if (values.at(i) == enum_) \ - return names.at(i); \ - return {}; \ - } \ - \ - static constexpr std::string toString(EnumName enum_) \ - { \ - for (std::size_t i = 0; i < size; ++i) \ - if (values.at(i) == enum_) \ - return std::string(names.at(i)); \ - return {}; \ - } \ - \ - static constexpr std::optional from_string( \ - std::string_view str \ - ) \ - { \ - for (std::size_t i = 0; i < size; ++i) \ - if (names.at(i) == str) \ - return values.at(i); \ - return std::nullopt; \ - } \ - \ - static constexpr std::optional from_stringCaseInsensitive( \ - std::string_view str \ - ) \ - { \ - static_assert( \ - !mstd::hasCaseInsensitiveCollision<(size)>(names), \ - "from_stringCaseInsensitive is ambiguous: this enum has " \ - "enumerator names that differ only by case" \ - ); \ - \ - for (std::size_t i = 0; i < names.size(); ++i) \ - if (mstd::iequals(names.at(i), str)) \ - return values.at(i); \ - return std::nullopt; \ - } \ - \ - static constexpr underlying_type to_underlying(EnumName enum_) \ - { \ - return static_cast(enum_); \ - } \ - \ - static constexpr std::optional index(EnumName enum_) \ - { \ - for (std::size_t i = 0; i < size; ++i) \ - if (values.at(i) == enum_) \ - return i; \ - return std::nullopt; \ - } \ - }; \ - \ - inline constexpr EnumName##Meta enum_meta(EnumName) { return {}; } - -#else -#define MSTD_ENUM(EnumName, Underlying, LIST) enum class EnumName : Underlying; -#endif - -#define MSTD_ENUM_BITFLAG(EnumName, Underlying, LIST) \ - MSTD_ENUM(EnumName, Underlying, LIST) \ - \ - inline constexpr EnumName operator|(EnumName lhs, EnumName rhs) \ - { \ - return static_cast( \ - static_cast(lhs) | static_cast(rhs) \ - ); \ - } \ - \ - inline constexpr EnumName& operator|=(EnumName& lhs, EnumName rhs) \ - { \ - lhs = lhs | rhs; \ - return lhs; \ - } \ - \ - inline constexpr EnumName operator~(EnumName value) \ - { \ - return static_cast(~static_cast(value)); \ - } \ - \ - struct EnumName##FlagTest \ - { \ - Underlying value; \ - constexpr operator EnumName() const noexcept \ - { \ - return static_cast(value); \ - } \ - \ - constexpr explicit operator bool() const noexcept \ - { \ - return value != Underlying{0}; \ - } \ - }; \ - \ - inline constexpr EnumName##FlagTest operator&(EnumName lhs, EnumName rhs) \ - { \ - return EnumName##FlagTest{static_cast( \ - static_cast(lhs) & static_cast(rhs) \ - )}; \ - } \ - \ - inline constexpr EnumName& operator&=(EnumName& lhs, EnumName rhs) \ - { \ - lhs = lhs & rhs; \ - return lhs; \ - } \ - \ - inline constexpr bool operator!(EnumName lhs) \ - { \ - return !static_cast(lhs); \ - } +#include "enum/enum.hpp" // IWYU pragma: export +#include "enum/enum_bit_flags.hpp" // IWYU pragma: export #endif // __MSTD__ENUM_HPP__ diff --git a/include/mstd/enum/enum.hpp b/include/mstd/enum/enum.hpp new file mode 100644 index 0000000..174ed0d --- /dev/null +++ b/include/mstd/enum/enum.hpp @@ -0,0 +1,150 @@ +/***************************************************************************** + + + mstd library + Copyright (C) 2025-now Jakob Gamper + + This program 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. + + This program 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 this program. If not, see . + + +******************************************************************************/ + +#ifndef __MSTD__ENUM__ENUM_HPP__ +#define __MSTD__ENUM__ENUM_HPP__ + +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include "mstd/enum/enum_string.hpp" // IWYU pragma: keep +#include "mstd/type_traits/enum_traits.hpp" // IWYU pragma: keep +#include "mstd/type_traits/string.hpp" // IWYU pragma: keep + +// +// Element expanders +// +// X(Name) -> Name, +// X(Name, 42) -> Name = 42, +#define MSTD_ENUM_MAKE_ENUM(name, ...) name __VA_OPT__(= __VA_ARGS__), + +// X(Name) or X(Name, 42) -> EnumName::Name, +#define MSTD_ENUM_MAKE_VALUE(name, ...) name, + +// X(Name) -> "Name", +#define MSTD_ENUM_MAKE_STRING(name, ...) #name, + +// ------------------------------------------------------------ +// Main macro +// ------------------------------------------------------------ +#ifndef Q_MOC_RUN +#define MSTD_ENUM(EnumName, Underlying, LIST) \ + enum class EnumName : Underlying \ + { \ + LIST(MSTD_ENUM_MAKE_ENUM) \ + }; \ + \ + struct EnumName##Meta \ + { \ + using type = EnumName; \ + using underlying_type = Underlying; \ + using enum EnumName; \ + static constexpr std::string_view EnumNameStr = #EnumName; \ + \ + static constexpr auto values = \ + std::to_array({LIST(MSTD_ENUM_MAKE_VALUE)}); \ + \ + static constexpr std::span values_view() \ + { \ + return values; \ + } \ + \ + static constexpr auto names = \ + std::to_array({LIST(MSTD_ENUM_MAKE_STRING)}); \ + \ + static constexpr std::size_t size = values.size(); \ + \ + static constexpr auto begin() { return values.begin(); } \ + static constexpr auto end() { return values.end(); } \ + \ + static constexpr std::string_view name(EnumName enum_) \ + { \ + for (std::size_t i = 0; i < size; ++i) \ + if (values.at(i) == enum_) \ + return names.at(i); \ + return {}; \ + } \ + \ + static constexpr std::string toString(EnumName enum_) \ + { \ + for (std::size_t i = 0; i < size; ++i) \ + if (values.at(i) == enum_) \ + return std::string(names.at(i)); \ + return {}; \ + } \ + \ + static constexpr std::optional from_string( \ + std::string_view str \ + ) \ + { \ + for (const auto &pair : mstd::EnumFromString::value) \ + if (pair.first == str) \ + return pair.second; \ + \ + for (std::size_t i = 0; i < size; ++i) \ + if (names.at(i) == str) \ + return values.at(i); \ + return std::nullopt; \ + } \ + \ + static constexpr std::optional from_stringCaseInsensitive( \ + std::string_view str \ + ) \ + { \ + static_assert( \ + !mstd::hasCaseInsensitiveCollision<(size)>(names), \ + "from_stringCaseInsensitive is ambiguous: this enum has " \ + "enumerator names that differ only by case" \ + ); \ + \ + for (std::size_t i = 0; i < names.size(); ++i) \ + if (mstd::iequals(names.at(i), str)) \ + return values.at(i); \ + return std::nullopt; \ + } \ + \ + static constexpr underlying_type to_underlying(EnumName enum_) \ + { \ + return static_cast(enum_); \ + } \ + \ + static constexpr std::optional index(EnumName enum_) \ + { \ + for (std::size_t i = 0; i < size; ++i) \ + if (values.at(i) == enum_) \ + return i; \ + return std::nullopt; \ + } \ + }; \ + \ + inline constexpr EnumName##Meta enum_meta(EnumName) { return {}; } + +#else +#define MSTD_ENUM(EnumName, Underlying, LIST) enum class EnumName : Underlying; +#endif + +#endif // __MSTD__ENUM__ENUM_HPP__ diff --git a/include/mstd/enum/enum_bit_flags.hpp b/include/mstd/enum/enum_bit_flags.hpp new file mode 100644 index 0000000..6f2e3f1 --- /dev/null +++ b/include/mstd/enum/enum_bit_flags.hpp @@ -0,0 +1,81 @@ +/***************************************************************************** + + + mstd library + Copyright (C) 2025-now Jakob Gamper + + This program 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. + + This program 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 this program. If not, see . + + +******************************************************************************/ + +#ifndef __MSTD__ENUM__ENUM_BIT_FLAGS_HPP__ +#define __MSTD__ENUM__ENUM_BIT_FLAGS_HPP__ + +#include "enum.hpp" + +#define MSTD_ENUM_BITFLAG(EnumName, Underlying, LIST) \ + MSTD_ENUM(EnumName, Underlying, LIST) \ + \ + inline constexpr EnumName operator|(EnumName lhs, EnumName rhs) \ + { \ + return static_cast( \ + static_cast(lhs) | static_cast(rhs) \ + ); \ + } \ + \ + inline constexpr EnumName& operator|=(EnumName& lhs, EnumName rhs) \ + { \ + lhs = lhs | rhs; \ + return lhs; \ + } \ + \ + inline constexpr EnumName operator~(EnumName value) \ + { \ + return static_cast(~static_cast(value)); \ + } \ + \ + struct EnumName##FlagTest \ + { \ + Underlying value; \ + constexpr operator EnumName() const noexcept \ + { \ + return static_cast(value); \ + } \ + \ + constexpr explicit operator bool() const noexcept \ + { \ + return value != Underlying{0}; \ + } \ + }; \ + \ + inline constexpr EnumName##FlagTest operator&(EnumName lhs, EnumName rhs) \ + { \ + return EnumName##FlagTest{static_cast( \ + static_cast(lhs) & static_cast(rhs) \ + )}; \ + } \ + \ + inline constexpr EnumName& operator&=(EnumName& lhs, EnumName rhs) \ + { \ + lhs = lhs & rhs; \ + return lhs; \ + } \ + \ + inline constexpr bool operator!(EnumName lhs) \ + { \ + return !static_cast(lhs); \ + } + +#endif // __MSTD__ENUM__ENUM_BIT_FLAGS_HPP__ diff --git a/include/mstd/enum/enum_string.hpp b/include/mstd/enum/enum_string.hpp new file mode 100644 index 0000000..94e9354 --- /dev/null +++ b/include/mstd/enum/enum_string.hpp @@ -0,0 +1,59 @@ +/***************************************************************************** + + + mstd library + Copyright (C) 2025-now Jakob Gamper + + This program 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. + + This program 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 this program. If not, see . + + +******************************************************************************/ + +#ifndef __MSTD__ENUM__ENUM_STRING_HPP__ +#define __MSTD__ENUM__ENUM_STRING_HPP__ + +#include +#include +#include + +namespace mstd +{ + /** + * @brief Provides compile-time mappings between enum values and their + * string representations. + * + * @tparam T The enum type for which the mappings are provided. + */ + template + struct EnumFromString + { + static constexpr std::array, 0> + value{}; + }; + + /** + * @brief Provides compile-time mappings between enum values and their + * string representations. + * + * @tparam T The enum type for which the mappings are provided. + */ + template + struct EnumToString + { + static constexpr std::array, 0> + value{}; + }; +} // namespace mstd + +#endif // __MSTD__ENUM__ENUM_STRING_HPP__ diff --git a/include/mstd/types/strong_type.hpp b/include/mstd/types/strong_type.hpp index e46b596..2621323 100644 --- a/include/mstd/types/strong_type.hpp +++ b/include/mstd/types/strong_type.hpp @@ -44,7 +44,7 @@ namespace mstd X(BOOLEAN, 1U << 5) // NOLINTNEXTLINE(hicpp-signed-bitwise) - MSTD_ENUM_BITFLAG(StrongTypeTrait, unsigned, STRONG_TYPE_TRAIT_LIST); + MSTD_ENUM_BITFLAG(StrongTypeTrait, unsigned, STRONG_TYPE_TRAIT_LIST); // cppcheck-suppress syntaxError /** * @brief A generalized strong type wrapper. diff --git a/test/enum/CMakeLists.txt b/test/enum/CMakeLists.txt new file mode 100644 index 0000000..531bd7e --- /dev/null +++ b/test/enum/CMakeLists.txt @@ -0,0 +1,66 @@ +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + cmake_minimum_required(VERSION 3.20) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + project(mstd_tests_enum LANGUAGES CXX) + include(CTest) + enable_testing() + set(MSTD_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../..") +else() + set(MSTD_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/..") +endif() + +if(NOT TARGET mstd) + add_library(mstd INTERFACE) + target_include_directories(mstd + INTERFACE + "${MSTD_ROOT_DIR}/include" + ) + target_compile_features(mstd INTERFACE cxx_std_20) +endif() + +if(NOT TARGET Catch2::Catch2WithMain) + add_subdirectory( + "${MSTD_ROOT_DIR}/external/Catch2" + "${CMAKE_CURRENT_BINARY_DIR}/external/Catch2" + ) +endif() + +list(APPEND CMAKE_MODULE_PATH "${MSTD_ROOT_DIR}/external/Catch2/extras") + +if(TARGET mstd_test_support) + set(MSTD_TEST_LINK_TARGET mstd_test_support) +else() + add_library(mstd_test_support INTERFACE) + if(EXISTS "${MSTD_ROOT_DIR}/test/include") + target_include_directories(mstd_test_support + INTERFACE + "${MSTD_ROOT_DIR}/test/include" + ) + endif() + target_link_libraries(mstd_test_support + INTERFACE + mstd + Catch2::Catch2WithMain + ) + target_compile_features(mstd_test_support INTERFACE cxx_std_20) + set(MSTD_TEST_LINK_TARGET mstd_test_support) +endif() + +add_executable(mstd_tests_enum + test_enum.cpp +) + +target_link_libraries(mstd_tests_enum + PRIVATE + "${MSTD_TEST_LINK_TARGET}" +) + +target_compile_features(mstd_tests_enum PRIVATE cxx_std_20) + +include(Catch) +catch_discover_tests(mstd_tests_enum + TEST_PREFIX "mstd::enum::" + REPORTER compact +) + +set_property(GLOBAL APPEND PROPERTY MSTD_TEST_TARGETS mstd_tests_enum) diff --git a/test/enum/test_enum.cpp b/test/enum/test_enum.cpp new file mode 100644 index 0000000..3198305 --- /dev/null +++ b/test/enum/test_enum.cpp @@ -0,0 +1,551 @@ +/***************************************************************************** + + + mstd library + Copyright (C) 2025-now Jakob Gamper + + This program 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. + + This program 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 this program. If not, see . + + +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mstd/enum.hpp" +#include "mstd/type_traits.hpp" + +namespace +{ + // Sequential, implicitly-valued enum (mirrors the common X-macro usage + // without any explicit enumerator values). +#define MSTD_TEST_COLOR_LIST(X) \ + X(Red) \ + X(Green) \ + X(Blue) + + MSTD_ENUM(Color, int, MSTD_TEST_COLOR_LIST) + + // Mixed enum: some enumerators have explicit values (including gaps and + // a non-zero start), others fall back to "previous value + 1", exactly + // like mstd/quantity/enums.hpp does in production code. +#define MSTD_TEST_STATUS_LIST(X) \ + X(Ok, 0) \ + X(Warning, 10) \ + X(Error, 20) \ + X(Critical) + + MSTD_ENUM(Status, unsigned, MSTD_TEST_STATUS_LIST) + + // An enum whose names collide only by case must still be usable via the + // case-sensitive API even though from_stringCaseInsensitive would be + // ill-formed for it; keep this one collision-free and dedicated to + // checking that enums nested in namespaces work identically. + namespace nested + { +#define MSTD_TEST_DIRECTION_LIST(X) \ + X(North) \ + X(East) \ + X(South) \ + X(West) + + MSTD_ENUM(Direction, unsigned char, MSTD_TEST_DIRECTION_LIST) + } // namespace nested + + // Power-of-two valued bitflag enum, exercising MSTD_ENUM_BITFLAG on top + // of everything MSTD_ENUM already provides. A byte-sized underlying type + // keeps the operator~ results easy to reason about in assertions. +#define MSTD_TEST_PERMISSION_LIST(X) \ + X(None, 0) \ + X(Read, 1) \ + X(Write, 2) \ + X(Execute, 4) + + MSTD_ENUM_BITFLAG(Permission, std::uint8_t, MSTD_TEST_PERMISSION_LIST) +} // namespace + +TEST_CASE("MSTD_ENUM assigns sequential implicit values", "[enum]") +{ + STATIC_REQUIRE(static_cast(Color::Red) == 0); + STATIC_REQUIRE(static_cast(Color::Green) == 1); + STATIC_REQUIRE(static_cast(Color::Blue) == 2); +} + +TEST_CASE( + "MSTD_ENUM honours explicit values and resumes sequentially after them", + "[enum]" +) +{ + STATIC_REQUIRE(static_cast(Status::Ok) == 0); + STATIC_REQUIRE(static_cast(Status::Warning) == 10); + STATIC_REQUIRE(static_cast(Status::Error) == 20); + STATIC_REQUIRE(static_cast(Status::Critical) == 21); +} + +TEST_CASE( + "MSTD_ENUM produces an enum class with the requested underlying type", + "[enum]" +) +{ + STATIC_REQUIRE(std::is_same_v, int>); + STATIC_REQUIRE(std::is_same_v, unsigned>); + STATIC_REQUIRE(std::is_enum_v); + STATIC_REQUIRE_FALSE(std::is_convertible_v); + STATIC_REQUIRE(std::is_enum_v); + STATIC_REQUIRE_FALSE(std::is_convertible_v); +} + +TEST_CASE( + "Meta::type and Meta::underlying_type alias the enum and its underlying " + "type", + "[enum][meta]" +) +{ + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); +} + +TEST_CASE("Meta::EnumNameStr holds the stringified enum name", "[enum][meta]") +{ + STATIC_REQUIRE(ColorMeta::EnumNameStr == "Color"); + STATIC_REQUIRE(StatusMeta::EnumNameStr == "Status"); + STATIC_REQUIRE(nested::DirectionMeta::EnumNameStr == "Direction"); +} + +TEST_CASE("Meta::size matches the number of enumerators", "[enum][meta]") +{ + STATIC_REQUIRE(ColorMeta::size == 3); + STATIC_REQUIRE(StatusMeta::size == 4); + STATIC_REQUIRE(nested::DirectionMeta::size == 4); +} + +TEST_CASE( + "Meta::values lists every enumerator in declaration order", + "[enum][meta]" +) +{ + STATIC_REQUIRE(ColorMeta::values.size() == 3); + STATIC_REQUIRE(ColorMeta::values[0] == Color::Red); + STATIC_REQUIRE(ColorMeta::values[1] == Color::Green); + STATIC_REQUIRE(ColorMeta::values[2] == Color::Blue); + + STATIC_REQUIRE(StatusMeta::values.size() == 4); + STATIC_REQUIRE(StatusMeta::values[0] == Status::Ok); + STATIC_REQUIRE(StatusMeta::values[1] == Status::Warning); + STATIC_REQUIRE(StatusMeta::values[2] == Status::Error); + STATIC_REQUIRE(StatusMeta::values[3] == Status::Critical); +} + +TEST_CASE( + "Meta::values_view exposes the values array as a span", + "[enum][meta]" +) +{ + constexpr auto view = ColorMeta::values_view(); + STATIC_REQUIRE(view.size() == ColorMeta::values.size()); + STATIC_REQUIRE(view.data() == ColorMeta::values.data()); + + REQUIRE(view.size() == 3); + REQUIRE(view[0] == Color::Red); + REQUIRE(view[1] == Color::Green); + REQUIRE(view[2] == Color::Blue); +} + +TEST_CASE( + "Meta::names lists every enumerator name in declaration order", + "[enum][meta]" +) +{ + STATIC_REQUIRE(ColorMeta::names.size() == 3); + STATIC_REQUIRE(ColorMeta::names[0] == "Red"); + STATIC_REQUIRE(ColorMeta::names[1] == "Green"); + STATIC_REQUIRE(ColorMeta::names[2] == "Blue"); + + STATIC_REQUIRE(StatusMeta::names[0] == "Ok"); + STATIC_REQUIRE(StatusMeta::names[1] == "Warning"); + STATIC_REQUIRE(StatusMeta::names[2] == "Error"); + STATIC_REQUIRE(StatusMeta::names[3] == "Critical"); +} + +TEST_CASE( + "Meta::begin/end iterate over values in declaration order", + "[enum][meta]" +) +{ + std::vector collected; + for (auto it = ColorMeta::begin(); it != ColorMeta::end(); ++it) + collected.push_back(*it); + + REQUIRE( + collected == std::vector{Color::Red, Color::Green, Color::Blue} + ); + + // Also confirm the class is usable with range-based for via begin()/end(). + const auto count = std::distance( + std::begin(ColorMeta::values), + std::end(ColorMeta::values) + ); + REQUIRE(count == 3); +} + +TEST_CASE("Meta::name resolves every declared enumerator", "[enum][meta]") +{ + STATIC_REQUIRE(ColorMeta::name(Color::Red) == "Red"); + STATIC_REQUIRE(ColorMeta::name(Color::Green) == "Green"); + STATIC_REQUIRE(ColorMeta::name(Color::Blue) == "Blue"); + + STATIC_REQUIRE(StatusMeta::name(Status::Warning) == "Warning"); + STATIC_REQUIRE(StatusMeta::name(Status::Critical) == "Critical"); +} + +TEST_CASE( + "Meta::name returns an empty string_view for an out-of-range value", + "[enum][meta]" +) +{ + constexpr auto bogus = static_cast(99); + STATIC_REQUIRE(ColorMeta::name(bogus) == std::string_view{}); + STATIC_REQUIRE(ColorMeta::name(bogus).empty()); +} + +TEST_CASE( + "Meta::toString mirrors Meta::name but returns std::string", + "[enum][meta]" +) +{ + REQUIRE(ColorMeta::toString(Color::Red) == std::string("Red")); + REQUIRE(ColorMeta::toString(Color::Blue) == std::string("Blue")); + REQUIRE(StatusMeta::toString(Status::Error) == std::string("Error")); + + constexpr auto bogus = static_cast(255); + REQUIRE(StatusMeta::toString(bogus).empty()); +} + +TEST_CASE( + "Meta::from_string parses exact, case-sensitive enumerator names", + "[enum][meta]" +) +{ + STATIC_REQUIRE(ColorMeta::from_string("Red") == Color::Red); + STATIC_REQUIRE(ColorMeta::from_string("Green") == Color::Green); + STATIC_REQUIRE(ColorMeta::from_string("Blue") == Color::Blue); + + STATIC_REQUIRE(StatusMeta::from_string("Critical") == Status::Critical); +} + +TEST_CASE( + "Meta::from_string rejects unknown, mismatched-case, or empty input", + "[enum][meta]" +) +{ + STATIC_REQUIRE(ColorMeta::from_string("Purple") == std::nullopt); + STATIC_REQUIRE(ColorMeta::from_string("red") == std::nullopt); + STATIC_REQUIRE(ColorMeta::from_string("") == std::nullopt); + STATIC_REQUIRE(ColorMeta::from_string("Red ") == std::nullopt); +} + +TEST_CASE( + "Meta::from_stringCaseInsensitive matches regardless of case", + "[enum][meta]" +) +{ + STATIC_REQUIRE(ColorMeta::from_stringCaseInsensitive("red") == Color::Red); + STATIC_REQUIRE(ColorMeta::from_stringCaseInsensitive("RED") == Color::Red); + STATIC_REQUIRE(ColorMeta::from_stringCaseInsensitive("ReD") == Color::Red); + STATIC_REQUIRE( + ColorMeta::from_stringCaseInsensitive("Blue") == Color::Blue + ); + + STATIC_REQUIRE( + nested::DirectionMeta::from_stringCaseInsensitive("north") == + nested::Direction::North + ); +} + +TEST_CASE( + "Meta::from_stringCaseInsensitive rejects unknown input", + "[enum][meta]" +) +{ + STATIC_REQUIRE( + ColorMeta::from_stringCaseInsensitive("purple") == std::nullopt + ); + STATIC_REQUIRE(ColorMeta::from_stringCaseInsensitive("") == std::nullopt); +} + +TEST_CASE( + "Meta::to_underlying returns the exact underlying representation", + "[enum][meta]" +) +{ + STATIC_REQUIRE(ColorMeta::to_underlying(Color::Red) == 0); + STATIC_REQUIRE(ColorMeta::to_underlying(Color::Green) == 1); + STATIC_REQUIRE(ColorMeta::to_underlying(Color::Blue) == 2); + + STATIC_REQUIRE(StatusMeta::to_underlying(Status::Ok) == 0U); + STATIC_REQUIRE(StatusMeta::to_underlying(Status::Warning) == 10U); + STATIC_REQUIRE(StatusMeta::to_underlying(Status::Error) == 20U); + STATIC_REQUIRE(StatusMeta::to_underlying(Status::Critical) == 21U); + + STATIC_REQUIRE( + std::is_same_v + ); +} + +TEST_CASE( + "Meta::index returns the declaration-order position of a value", + "[enum][meta]" +) +{ + STATIC_REQUIRE(ColorMeta::index(Color::Red) == 0); + STATIC_REQUIRE(ColorMeta::index(Color::Green) == 1); + STATIC_REQUIRE(ColorMeta::index(Color::Blue) == 2); + + STATIC_REQUIRE(StatusMeta::index(Status::Ok) == 0); + STATIC_REQUIRE(StatusMeta::index(Status::Critical) == 3); +} + +TEST_CASE( + "Meta::index returns nullopt for an out-of-range value", + "[enum][meta]" +) +{ + constexpr auto bogus = static_cast(-1); + STATIC_REQUIRE(ColorMeta::index(bogus) == std::nullopt); +} + +TEST_CASE( + "enum_meta(EnumName) is discoverable via ADL and returns the Meta type", + "[enum][meta]" +) +{ + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v< + decltype(enum_meta(nested::Direction{})), + nested::DirectionMeta> + ); +} + +TEST_CASE( + "mstd::enum_meta_t and mstd::has_enum_meta reflect MSTD_ENUM-generated " + "types", + "[enum][traits]" +) +{ + STATIC_REQUIRE(std::is_same_v, ColorMeta>); + STATIC_REQUIRE(std::is_same_v, StatusMeta>); + + STATIC_REQUIRE(mstd::has_enum_meta); + STATIC_REQUIRE(mstd::has_enum_meta); + STATIC_REQUIRE(mstd::has_enum_meta); +} + +namespace +{ + // A plain enum, not declared via MSTD_ENUM, used to confirm that + // has_enum_meta correctly reports false for types without generated + // metadata. + enum class PlainEnum + { + A, + B + }; +} // namespace + +TEST_CASE( + "mstd::has_enum_meta is false for types without generated metadata", + "[enum][traits]" +) +{ + STATIC_REQUIRE_FALSE(mstd::has_enum_meta); + STATIC_REQUIRE_FALSE(mstd::has_enum_meta); +} + +TEST_CASE( + "Round-tripping every enumerator through name/from_string is stable", + "[enum][meta]" +) +{ + for (const auto value : ColorMeta::values) + { + const auto name = ColorMeta::name(value); + REQUIRE_FALSE(name.empty()); + REQUIRE(ColorMeta::from_string(name) == value); + REQUIRE(ColorMeta::from_stringCaseInsensitive(name) == value); + REQUIRE(ColorMeta::to_underlying(value) == static_cast(value)); + } + + for (const auto value : StatusMeta::values) + { + const auto name = StatusMeta::name(value); + REQUIRE_FALSE(name.empty()); + REQUIRE(StatusMeta::from_string(name) == value); + const auto idx = StatusMeta::index(value); + REQUIRE(idx.has_value()); + REQUIRE(StatusMeta::values[*idx] == value); + } +} + +// ----------------------------------------------------------------------- +// MSTD_ENUM_BITFLAG +// ----------------------------------------------------------------------- + +TEST_CASE( + "MSTD_ENUM_BITFLAG still generates the full MSTD_ENUM metadata", + "[enum][bitflag]" +) +{ + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE( + std::is_same_v + ); + STATIC_REQUIRE(PermissionMeta::size == 4); + STATIC_REQUIRE(PermissionMeta::name(Permission::Read) == "Read"); + STATIC_REQUIRE( + PermissionMeta::from_string("Execute") == Permission::Execute + ); + STATIC_REQUIRE(PermissionMeta::to_underlying(Permission::Write) == 2U); +} + +TEST_CASE( + "operator| combines bitflags via a bitwise OR of the underlying value", + "[enum][bitflag]" +) +{ + constexpr auto combined = Permission::Read | Permission::Write; + STATIC_REQUIRE( + PermissionMeta::to_underlying(combined) == 0b011 + ); + + constexpr auto all = Permission::Read | Permission::Write + | Permission::Execute; + STATIC_REQUIRE(PermissionMeta::to_underlying(all) == 0b111); + + // Combining with None is a no-op. + STATIC_REQUIRE( + (Permission::Read | Permission::None) == Permission::Read + ); +} + +TEST_CASE( + "operator|= mutates the left-hand side in place", + "[enum][bitflag]" +) +{ + auto flags = Permission::Read; + flags |= Permission::Write; + REQUIRE(PermissionMeta::to_underlying(flags) == 0b011); + + flags |= Permission::Execute; + REQUIRE(PermissionMeta::to_underlying(flags) == 0b111); + + // Re-applying an already-set bit changes nothing. + flags |= Permission::Read; + REQUIRE(PermissionMeta::to_underlying(flags) == 0b111); +} + +TEST_CASE( + "operator~ inverts every bit of the underlying representation", + "[enum][bitflag]" +) +{ + STATIC_REQUIRE( + PermissionMeta::to_underlying(~Permission::None) == 0xFF + ); + STATIC_REQUIRE( + PermissionMeta::to_underlying(~Permission::Read) == 0xFE + ); + STATIC_REQUIRE( + PermissionMeta::to_underlying(~(~Permission::Read)) == 0x01 + ); +} + +TEST_CASE( + "operator& yields a FlagTest that reports whether any bit overlaps", + "[enum][bitflag]" +) +{ + constexpr auto readWrite = Permission::Read | Permission::Write; + + STATIC_REQUIRE(static_cast(readWrite & Permission::Read)); + STATIC_REQUIRE(static_cast(readWrite & Permission::Write)); + STATIC_REQUIRE_FALSE(static_cast(readWrite & Permission::Execute)); + STATIC_REQUIRE_FALSE(static_cast(readWrite & Permission::None)); + + if (readWrite & Permission::Read) + SUCCEED("FlagTest is usable directly in a boolean context"); + else + FAIL("expected the Read bit to be set"); +} + +TEST_CASE( + "operator& converts back to EnumName, exposing the shared bits", + "[enum][bitflag]" +) +{ + constexpr auto readWrite = Permission::Read | Permission::Write; + constexpr Permission shared = readWrite & (Permission::Write | Permission::Execute); + + STATIC_REQUIRE(shared == Permission::Write); + STATIC_REQUIRE(PermissionMeta::to_underlying(shared) == 0b010); +} + +TEST_CASE( + "operator&= narrows the left-hand side to the intersection of flags", + "[enum][bitflag]" +) +{ + auto flags = Permission::Read | Permission::Write | Permission::Execute; + flags &= (Permission::Write | Permission::Execute); + REQUIRE(PermissionMeta::to_underlying(flags) == 0b110); + + flags &= Permission::None; + REQUIRE(PermissionMeta::to_underlying(flags) == 0); +} + +TEST_CASE( + "operator! reports whether an enum value has no bits set", + "[enum][bitflag]" +) +{ + STATIC_REQUIRE(!Permission::None); + STATIC_REQUIRE_FALSE(!Permission::Read); + STATIC_REQUIRE_FALSE(!(Permission::Read | Permission::Write)); +} + +TEST_CASE( + "Bitflag operators compose to implement a typical has-flag check", + "[enum][bitflag]" +) +{ + constexpr auto granted = Permission::Read | Permission::Execute; + + auto hasFlag = [](Permission value, Permission flag) + { return static_cast(value & flag); }; + + REQUIRE(hasFlag(granted, Permission::Read)); + REQUIRE(hasFlag(granted, Permission::Execute)); + REQUIRE_FALSE(hasFlag(granted, Permission::Write)); + REQUIRE_FALSE(hasFlag(granted, Permission::None)); +} diff --git a/test/math/test_cpow.cpp b/test/math/test_cpow.cpp index d078eda..62b5312 100644 --- a/test/math/test_cpow.cpp +++ b/test/math/test_cpow.cpp @@ -20,7 +20,6 @@ ******************************************************************************/ -#include #include #include #include @@ -43,4 +42,4 @@ TEST_CASE("cpow computes integer powers at compile-time", "[math][cpow]") STATIC_REQUIRE(cpow<-3>(2.0) == 0.125); STATIC_REQUIRE(cpow<-4>(2.0) == 0.0625); STATIC_REQUIRE(cpow<-5>(2.0) == 0.03125); -} \ No newline at end of file +}