diff --git a/src/utils/result.hpp b/src/utils/result.hpp index 6293549..0af019c 100644 --- a/src/utils/result.hpp +++ b/src/utils/result.hpp @@ -60,19 +60,19 @@ namespace detail { } NODISCARD optional value() const & { - return has_value() ? value_ : nullopt; + return has_value() ? optional(value_) : nullopt; } NODISCARD optional error() const & { - return is_error() ? error_ : nullopt; + return is_error() ? optional(error_) : nullopt; } NODISCARD optional value() && { - return has_value() ? std::move(value_) : nullopt; + return has_value() ? optional(std::move(value_)) : nullopt; } NODISCARD optional error() && { - return is_error() ? std::move(error_) : nullopt; + return is_error() ? optional(std::move(error_)) : nullopt; } NODISCARD T& unwrap_value() & { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 42e5a69..2642471 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -81,12 +81,14 @@ if(NOT CPPTRACE_SKIP_UNIT) add_executable( unittest unit/main.cpp - unit/raw_trace.cpp - unit/object_trace.cpp - unit/stacktrace.cpp - unit/from_current.cpp - unit/from_current_z.cpp - unit/traced_exception.cpp + unit/tracing/raw_trace.cpp + unit/tracing/object_trace.cpp + unit/tracing/stacktrace.cpp + unit/tracing/from_current.cpp + unit/tracing/from_current_z.cpp + unit/tracing/traced_exception.cpp + unit/internals/optional.cpp + unit/internals/result.cpp ) target_compile_features(unittest PRIVATE cxx_std_20) target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main) @@ -101,5 +103,6 @@ if(NOT CPPTRACE_SKIP_UNIT) if(CPPTRACE_SANITIZER_BUILD) target_compile_definitions(unittest PRIVATE CPPTRACE_SANITIZER_BUILD) endif() + target_include_directories(unittest PRIVATE ../src) add_test(NAME unittest COMMAND unittest) endif() diff --git a/test/unit/internals/optional.cpp b/test/unit/internals/optional.cpp new file mode 100644 index 0000000..f69d915 --- /dev/null +++ b/test/unit/internals/optional.cpp @@ -0,0 +1,119 @@ +#include + +#include "utils/optional.hpp" + +using namespace cpptrace::detail; + +TEST(OptionalTest, DefaultConstructor) { + optional o; + EXPECT_FALSE(o.has_value()); + EXPECT_FALSE(static_cast(o)); +} + +TEST(OptionalTest, ConstructWithNullopt) { + optional o(nullopt); + EXPECT_FALSE(o.has_value()); +} + +TEST(OptionalTest, ValueConstructor) { + optional o(42); + EXPECT_TRUE(o.has_value()); + EXPECT_EQ(o.unwrap(), 42); + + int x = 100; + optional o2(x); + EXPECT_TRUE(o2.has_value()); + EXPECT_EQ(o2.unwrap(), 100); +} + +TEST(OptionalTest, CopyConstructor) { + optional o1(42); + optional o2(o1); + EXPECT_TRUE(o2.has_value()); + EXPECT_EQ(o2.unwrap(), 42); + + optional o3(nullopt); + optional o4(o3); + EXPECT_FALSE(o4.has_value()); +} + +TEST(OptionalTest, MoveConstructor) { + optional o1(42); + optional o2(std::move(o1)); + EXPECT_TRUE(o2.has_value()); + EXPECT_EQ(o2.unwrap(), 42); + + optional o3(nullopt); + optional o4(std::move(o3)); + EXPECT_FALSE(o4.has_value()); +} + +TEST(OptionalTest, CopyAssignmentOperator) { + optional o1(42); + optional o2; + o2 = o1; + EXPECT_TRUE(o2.has_value()); + EXPECT_EQ(o2.unwrap(), 42); + + optional o3(nullopt); + optional o4(100); + o4 = o3; + EXPECT_FALSE(o4.has_value()); +} + +TEST(OptionalTest, MoveAssignmentOperator) { + optional o1(42); + optional o2; + o2 = std::move(o1); + EXPECT_TRUE(o2.has_value()); + EXPECT_EQ(o2.unwrap(), 42); + + optional o3(nullopt); + optional o4(99); + o4 = std::move(o3); + EXPECT_FALSE(o4.has_value()); +} + +TEST(OptionalTest, AssignmentFromValue) { + optional o; + o = 123; + EXPECT_TRUE(o.has_value()); + EXPECT_EQ(o.unwrap(), 123); + + o = nullopt; + EXPECT_FALSE(o.has_value()); +} + +TEST(OptionalTest, Reset) { + optional o(42); + EXPECT_TRUE(o.has_value()); + o.reset(); + EXPECT_FALSE(o.has_value()); +} + +TEST(OptionalTest, Swap) { + optional o1(42); + optional o2(100); + + o1.swap(o2); + EXPECT_TRUE(o1.has_value()); + EXPECT_TRUE(o2.has_value()); + EXPECT_EQ(o1.unwrap(), 100); + EXPECT_EQ(o2.unwrap(), 42); + + // Swap a value-holding optional with an empty optional + optional o3(7); + optional o4(nullopt); + o3.swap(o4); + EXPECT_FALSE(o3.has_value()); + EXPECT_TRUE(o4.has_value()); + EXPECT_EQ(o4.unwrap(), 7); +} + +TEST(OptionalTest, ValueOr) { + optional o1(42); + EXPECT_EQ(o1.value_or(100), 42); + + optional o2(nullopt); + EXPECT_EQ(o2.value_or(100), 100); +} diff --git a/test/unit/internals/result.cpp b/test/unit/internals/result.cpp new file mode 100644 index 0000000..b942139 --- /dev/null +++ b/test/unit/internals/result.cpp @@ -0,0 +1,98 @@ +#include + +#include "utils/result.hpp" + +using namespace cpptrace::detail; + +// A simple custom error type that behaves like a standard exception. +struct error { + int x; + const char* what() const { + return "error..."; + } +}; + +class ResultFixture : public testing::Test { +public: + ResultFixture() { + cpptrace::absorb_trace_exceptions(true); + } + + ~ResultFixture() override { + cpptrace::absorb_trace_exceptions(false); + } +}; + +TEST_F(ResultFixture, ConstructWithValueRValue) { + cpptrace::detail::Result result("test"); + EXPECT_TRUE(result.has_value()); + EXPECT_FALSE(result.is_error()); + EXPECT_TRUE(static_cast(result)); + + EXPECT_EQ(result.unwrap_value(), "test"); + EXPECT_FALSE(result.error().has_value()); +} + +TEST_F(ResultFixture, ConstructWithValueLValue) { + std::string s = "test"; + cpptrace::detail::Result result(s); + + EXPECT_TRUE(result.has_value()); + EXPECT_FALSE(result.is_error()); + EXPECT_EQ(result.unwrap_value(), "test"); + + s = "x"; + EXPECT_EQ(result.unwrap_value(), "test"); +} + +TEST_F(ResultFixture, ConstructWithErrorRValue) { + cpptrace::detail::Result result(error{1}); + EXPECT_FALSE(result.has_value()); + EXPECT_TRUE(result.is_error()); + EXPECT_FALSE(static_cast(result)); + + EXPECT_EQ(result.unwrap_error().x, 1); + + // Check that value() returns nullopt in this scenario + EXPECT_FALSE(result.value().has_value()); +} + +TEST_F(ResultFixture, ConstructWithErrorLValue) { + error e{1}; + cpptrace::detail::Result result(e); + + EXPECT_FALSE(result.has_value()); + EXPECT_TRUE(result.is_error()); + EXPECT_EQ(result.unwrap_error().x, 1); +} + +TEST_F(ResultFixture, MoveConstructorValue) { + cpptrace::detail::Result original(std::string("move")); + cpptrace::detail::Result moved(std::move(original)); + + EXPECT_TRUE(moved.has_value()); + EXPECT_EQ(moved.unwrap_value(), "move"); + EXPECT_TRUE(original.has_value()); +} + +TEST_F(ResultFixture, MoveConstructorError) { + cpptrace::detail::Result original(error{1}); + cpptrace::detail::Result moved(std::move(original)); + + EXPECT_TRUE(moved.is_error()); + EXPECT_EQ(moved.unwrap_error().x, 1); + EXPECT_TRUE(original.is_error()); +} + +TEST_F(ResultFixture, ValueOr) { + { + cpptrace::detail::Result res_with_value(42); + EXPECT_EQ(res_with_value.value_or(-1), 42); + EXPECT_EQ(std::move(res_with_value).value_or(-1), 42); + } + { + cpptrace::detail::Result res_with_error(error{}); + EXPECT_EQ(res_with_error.value_or(-1), -1); + EXPECT_EQ(std::move(res_with_error).value_or(-1), -1); + } +} diff --git a/test/unit/from_current.cpp b/test/unit/tracing/from_current.cpp similarity index 100% rename from test/unit/from_current.cpp rename to test/unit/tracing/from_current.cpp diff --git a/test/unit/from_current_z.cpp b/test/unit/tracing/from_current_z.cpp similarity index 100% rename from test/unit/from_current_z.cpp rename to test/unit/tracing/from_current_z.cpp diff --git a/test/unit/object_trace.cpp b/test/unit/tracing/object_trace.cpp similarity index 100% rename from test/unit/object_trace.cpp rename to test/unit/tracing/object_trace.cpp diff --git a/test/unit/raw_trace.cpp b/test/unit/tracing/raw_trace.cpp similarity index 100% rename from test/unit/raw_trace.cpp rename to test/unit/tracing/raw_trace.cpp diff --git a/test/unit/stacktrace.cpp b/test/unit/tracing/stacktrace.cpp similarity index 100% rename from test/unit/stacktrace.cpp rename to test/unit/tracing/stacktrace.cpp diff --git a/test/unit/traced_exception.cpp b/test/unit/tracing/traced_exception.cpp similarity index 100% rename from test/unit/traced_exception.cpp rename to test/unit/tracing/traced_exception.cpp