From 2a10eaae094e48a157d55ec886aaa07b0d0be6c9 Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Mon, 28 Oct 2024 23:52:48 +0100 Subject: Some work on test framework & option --- asl/maybe_uninit.hpp | 6 ++-- asl/option.hpp | 31 +++++++++++++++++ asl/testing/testing.cpp | 20 +++++------ asl/testing/testing.hpp | 8 +++++ asl/tests/format_tests.cpp | 40 ++++++++++----------- asl/tests/option_tests.cpp | 87 +++++++++++++++++++++++++++++++++++++--------- asl/tests/test_types.hpp | 30 ++++++++++++++++ 7 files changed, 173 insertions(+), 49 deletions(-) (limited to 'asl') diff --git a/asl/maybe_uninit.hpp b/asl/maybe_uninit.hpp index 19351d5..0ab09ee 100644 --- a/asl/maybe_uninit.hpp +++ b/asl/maybe_uninit.hpp @@ -39,9 +39,9 @@ public: constexpr T* init_ptr_unsafe() & { return &m_value; } // @Safety Reference must only be accessed when in initialized state. - constexpr T& as_init_unsafe() && = delete; - constexpr const T& as_init_unsafe() const& { return m_value; } - constexpr T& as_init_unsafe() & { return m_value; } + constexpr T&& as_init_unsafe() && { return ASL_MOVE(m_value); } + constexpr const T& as_init_unsafe() const& { return m_value; } + constexpr T& as_init_unsafe() & { return m_value; } // @Safety Must be called only when in uninitialized state. template diff --git a/asl/option.hpp b/asl/option.hpp index 7600252..ddd531d 100644 --- a/asl/option.hpp +++ b/asl/option.hpp @@ -40,6 +40,13 @@ public: constexpr option() = default; constexpr option(nullopt_t) {} // NOLINT(*-explicit-conversions) + template + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr option(U&& value) requires constructible + { + construct(ASL_FWD(value)); + } + constexpr option(const option& other) requires copy_constructible { if (other.m_has_value) @@ -116,6 +123,30 @@ public: m_has_value = false; } } + + constexpr bool has_value() const { return m_has_value; } + + // @Todo Do we want this on rvalues? Or maybe some kind of unwrap? + constexpr T&& value() && + { + ASL_ASSERT(m_has_value); // @Todo Release assert + return ASL_MOVE(m_payload).as_init_unsafe(); + } + + constexpr T& value() & + { + ASL_ASSERT(m_has_value); // @Todo Release assert + return m_payload.as_init_unsafe(); + } + + constexpr const T& value() const& + { + ASL_ASSERT(m_has_value); // @Todo Release assert + return m_payload.as_init_unsafe(); + } }; +template +option(T) -> option; + } // namespace asl diff --git a/asl/testing/testing.cpp b/asl/testing/testing.cpp index f0c4fb5..405df34 100644 --- a/asl/testing/testing.cpp +++ b/asl/testing/testing.cpp @@ -25,14 +25,14 @@ void asl::testing::report_failure(const char* msg, const char* file, int line) { asl::eprint("--------------------------------------------------------------\n"); asl::eprint("Test assertion failed at {}, line {}:\n", file, line); - asl::eprint(" {}:\n", msg); + asl::eprint(" {}\n", msg); asl::eprint("--------------------------------------------------------------\n"); g_current_test_fail = true; } #define RESET "\x1b[0m" -#define RED "\x1b[0;31m" -#define GREEN "\x1b[0;32m" +#define RED(S) "\x1b[0;31m" S RESET +#define GREEN(S) "\x1b[0;32m" S RESET int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { @@ -41,33 +41,33 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) for (auto* it = g_head; it != nullptr; it = it->m_next) { - asl::eprint(GREEN "[ RUN ]" RESET " {}\n", it->m_case_name); + asl::eprint(GREEN("[ RUN ]") " {}\n", it->m_case_name); g_current_test_fail = false; it->m_fn(); if (!g_current_test_fail) { - asl::eprint(GREEN "[ OK ]" RESET " {}\n", it->m_case_name); + asl::eprint(GREEN("[ OK ]") " {}\n", it->m_case_name); pass += 1; } else { - asl::eprint(RED "[ FAILED ]" RESET " {}\n", it->m_case_name); + asl::eprint(RED("[ FAILED ]") " {}\n", it->m_case_name); fail += 1; } } - asl::eprint(GREEN "[----------]" RESET " {} test(s) run\n", fail + pass); + asl::eprint(GREEN("[----------]") " {} test(s) run\n", fail + pass); if (fail == 0) { - asl::eprint(GREEN "[ PASSED ]" RESET " Good job!\n"); + asl::eprint(GREEN("[ PASSED ]") " Good job!\n"); } else { - asl::eprint(RED "[ FAILED ]" RESET " {} test(s) failed\n", fail); + asl::eprint(RED("[ FAILED ]") " {} test(s) failed\n", fail); } - return 0; + return fail; } diff --git a/asl/testing/testing.hpp b/asl/testing/testing.hpp index 4fe44df..bf8f54e 100644 --- a/asl/testing/testing.hpp +++ b/asl/testing/testing.hpp @@ -36,3 +36,11 @@ struct Test #CASE, \ asl_test_fn_##CASE); \ void asl_test_fn_##CASE() + +#define ASL_TEST_ASSERT(EXPR) \ + if (EXPR) {} \ + else { ::asl::testing::report_failure(#EXPR, __FILE__, __LINE__); return; } + +#define ASL_TEST_EXPECT(EXPR) \ + if (EXPR) {} \ + else { ::asl::testing::report_failure(#EXPR, __FILE__, __LINE__); } diff --git a/asl/tests/format_tests.cpp b/asl/tests/format_tests.cpp index 6e2430d..f4ca2cf 100644 --- a/asl/tests/format_tests.cpp +++ b/asl/tests/format_tests.cpp @@ -1,4 +1,5 @@ #include "asl/format.hpp" +#include "asl/testing/testing.hpp" #include #include @@ -33,74 +34,73 @@ public: } }; -int main2() +ASL_TEST(Format) { StringSink sink; - // @Todo Use the testing framework + // @Todo Introduce ASL_TEST_ASSERT_EQ, or ASL_TEST_ASSERT_STREQ + // @Todo Don't use strcmp for string comparison asl::format(&sink, "Hello, world!"); - assert(strcmp(sink.cstr(), "Hello, world!") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, world!") == 0); sink.reset(); asl::format(&sink, ""); - assert(strcmp(sink.cstr(), "") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "") == 0); sink.reset(); asl::format(&sink, "Hello, {}!", "world"); - assert(strcmp(sink.cstr(), "Hello, world!") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, world!") == 0); sink.reset(); asl::format(&sink, "Hello, {}! {}", "world"); - assert(strcmp(sink.cstr(), "Hello, world! ") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, world! ") == 0); sink.reset(); asl::format(&sink, "Hello, pup!", "world"); - assert(strcmp(sink.cstr(), "Hello, pup!") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, pup!") == 0); sink.reset(); asl::format(&sink, "{}", "CHEESE"); - assert(strcmp(sink.cstr(), "CHEESE") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "CHEESE") == 0); sink.reset(); asl::format(&sink, "{ ", "CHEESE"); - assert(strcmp(sink.cstr(), " ") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), " ") == 0); sink.reset(); asl::format(&sink, "{", "CHEESE"); - assert(strcmp(sink.cstr(), "") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "") == 0); sink.reset(); asl::format(&sink, "a{{b"); - assert(strcmp(sink.cstr(), "a{b") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "a{b") == 0); sink.reset(); asl::format(&sink, "{{{}}} }", "CHEESE"); - assert(strcmp(sink.cstr(), "{CHEESE} }") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "{CHEESE} }") == 0); sink.reset(); asl::format(&sink, "{} {} {}", 0, 1, 2); - assert(strcmp(sink.cstr(), "0 1 2") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "0 1 2") == 0); sink.reset(); asl::format(&sink, "{} {} {}", 10, 11, 12); - assert(strcmp(sink.cstr(), "10 11 12") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "10 11 12") == 0); sink.reset(); asl::format(&sink, "{} {} {}", 100, 101, 102); - assert(strcmp(sink.cstr(), "100 101 102") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "100 101 102") == 0); sink.reset(); asl::format(&sink, "{} {} {}", 1000, 1001, 1002); - assert(strcmp(sink.cstr(), "1000 1001 1002") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "1000 1001 1002") == 0); sink.reset(); asl::format(&sink, "{} {} {} {}", -1, -23, -456, -7890); - assert(strcmp(sink.cstr(), "-1 -23 -456 -7890") == 0); + ASL_TEST_ASSERT(strcmp(sink.cstr(), "-1 -23 -456 -7890") == 0); sink.reset(); asl::format(&sink, "{} {}", true, false); - assert(strcmp(sink.cstr(), "true false") == 0); - - return 0; + ASL_TEST_ASSERT(strcmp(sink.cstr(), "true false") == 0); } diff --git a/asl/tests/option_tests.cpp b/asl/tests/option_tests.cpp index e423cc9..e7fff8b 100644 --- a/asl/tests/option_tests.cpp +++ b/asl/tests/option_tests.cpp @@ -24,35 +24,90 @@ static_assert(asl::move_assignable>); static_assert(asl::move_assignable>); static_assert(!asl::move_assignable>); -ASL_TEST(Option_cheese) +ASL_TEST(make_null) { asl::option a; - asl::option b; + asl::option b = asl::nullopt; - a = ASL_MOVE(b); + ASL_TEST_EXPECT(!a.has_value()); + ASL_TEST_EXPECT(!b.has_value()); } -ASL_TEST(Option_cheese2) +ASL_TEST(make_value) { - asl::option a; - asl::option b; + asl::option a = 48; - a = ASL_MOVE(b); + ASL_TEST_EXPECT(a.has_value()); } -ASL_TEST(Option_cheese3) +ASL_TEST(reset) { - asl::option a; - asl::option b; + asl::option b = 48; + ASL_TEST_EXPECT(b.has_value()); + + b.reset(); + ASL_TEST_EXPECT(!b.has_value()); +} + +ASL_TEST(call_destructor) +{ + bool destroyed = false; + + { + DestructorObserver obs(&destroyed); + + asl::option opt(ASL_MOVE(obs)); + ASL_TEST_EXPECT(!destroyed); + + asl::option opt2 = ASL_MOVE(opt); + ASL_TEST_EXPECT(!destroyed); + } - a = ASL_MOVE(b); - asl::testing::report_failure("OH NO", __FILE__, __LINE__); + ASL_TEST_EXPECT(destroyed); } -ASL_TEST(Option_cheese4) +ASL_TEST(call_destructor_on_reset) { - asl::option a; - asl::option b; + bool destroyed = false; + + asl::option opt(&destroyed); + ASL_TEST_EXPECT(!destroyed); + + opt.reset(); + ASL_TEST_EXPECT(destroyed); +} + +ASL_TEST(value) +{ + asl::option a = 1; + asl::option b = 2; + asl::option c = a; + + ASL_TEST_EXPECT(a.value() == 1); + ASL_TEST_EXPECT(b.value() == 2); + ASL_TEST_EXPECT(c.value() == 1); - a = ASL_MOVE(b); + c = b; + ASL_TEST_EXPECT(c.value() == 2); +} + +ASL_TEST(value_move) +{ + bool destroyed = false; + + asl::option opt(&destroyed); + ASL_TEST_EXPECT(!destroyed); + + { + auto x = ASL_MOVE(opt).value(); + ASL_TEST_EXPECT(!destroyed); + } + + ASL_TEST_EXPECT(destroyed); +} + +ASL_TEST(deduction_guide) +{ + asl::option opt(45); + ASL_TEST_EXPECT(opt.value() == 45); } diff --git a/asl/tests/test_types.hpp b/asl/tests/test_types.hpp index da1faa6..cabc084 100644 --- a/asl/tests/test_types.hpp +++ b/asl/tests/test_types.hpp @@ -1,5 +1,7 @@ #pragma once +#include "asl/utility.hpp" + struct DefaultConstructible { DefaultConstructible() {} }; struct TriviallyDefaultConstructible { TriviallyDefaultConstructible() = default; }; struct NonDefaultConstructible { NonDefaultConstructible() = delete; }; @@ -23,3 +25,31 @@ struct NonMoveAssignable { NonMoveAssignable& operator=(NonMoveAssignable&&) = d struct TriviallyDestructible { ~TriviallyDestructible() = default; }; struct HasDestructor { ~HasDestructor() {} }; +struct DestructorObserver +{ + bool* destroyed; + + explicit DestructorObserver(bool* destroyed_) : destroyed{destroyed_} {} + + DestructorObserver(DestructorObserver&& other) + : destroyed{asl::exchange(other.destroyed, nullptr)} + {} + + DestructorObserver& operator=(DestructorObserver&& other) + { + if (this != &other) + { + destroyed = asl::exchange(other.destroyed, nullptr); + } + return *this; + } + + ~DestructorObserver() + { + if (destroyed != nullptr) + { + *destroyed = true; + } + } +}; + -- cgit