Some work on test framework & option

This commit is contained in:
2024-10-28 23:52:48 +01:00
parent 46cc6bfc5f
commit 2a10eaae09
9 changed files with 176 additions and 51 deletions

View File

@ -17,4 +17,4 @@ build --cxxopt=-Wno-extra-semi-stmt
build --cxxopt=-Wno-extra-semi build --cxxopt=-Wno-extra-semi
build --cxxopt=-Wno-global-constructors build --cxxopt=-Wno-global-constructors
test --test_output=all test --test_output=errors

View File

@ -28,3 +28,4 @@ Checks:
- "-*-no-array-decay" - "-*-no-array-decay"
- "-*-signed-bitwise" - "-*-signed-bitwise"
- "-readability-use-anyofallof" - "-readability-use-anyofallof"
- "-readability-function-cognitive-complexity"

View File

@ -39,9 +39,9 @@ public:
constexpr T* init_ptr_unsafe() & { return &m_value; } constexpr T* init_ptr_unsafe() & { return &m_value; }
// @Safety Reference must only be accessed when in initialized state. // @Safety Reference must only be accessed when in initialized state.
constexpr T& as_init_unsafe() && = delete; constexpr T&& as_init_unsafe() && { return ASL_MOVE(m_value); }
constexpr const T& as_init_unsafe() const& { return m_value; } constexpr const T& as_init_unsafe() const& { return m_value; }
constexpr T& as_init_unsafe() & { return m_value; } constexpr T& as_init_unsafe() & { return m_value; }
// @Safety Must be called only when in uninitialized state. // @Safety Must be called only when in uninitialized state.
template<typename... Args> template<typename... Args>

View File

@ -40,6 +40,13 @@ public:
constexpr option() = default; constexpr option() = default;
constexpr option(nullopt_t) {} // NOLINT(*-explicit-conversions) constexpr option(nullopt_t) {} // NOLINT(*-explicit-conversions)
template<typename U>
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr option(U&& value) requires constructible<T, U>
{
construct(ASL_FWD(value));
}
constexpr option(const option& other) requires copy_constructible<T> constexpr option(const option& other) requires copy_constructible<T>
{ {
if (other.m_has_value) if (other.m_has_value)
@ -116,6 +123,30 @@ public:
m_has_value = false; 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<typename T>
option(T) -> option<T>;
} // namespace asl } // namespace asl

View File

@ -25,14 +25,14 @@ void asl::testing::report_failure(const char* msg, const char* file, int line)
{ {
asl::eprint("--------------------------------------------------------------\n"); asl::eprint("--------------------------------------------------------------\n");
asl::eprint("Test assertion failed at {}, line {}:\n", file, line); asl::eprint("Test assertion failed at {}, line {}:\n", file, line);
asl::eprint(" {}:\n", msg); asl::eprint(" {}\n", msg);
asl::eprint("--------------------------------------------------------------\n"); asl::eprint("--------------------------------------------------------------\n");
g_current_test_fail = true; g_current_test_fail = true;
} }
#define RESET "\x1b[0m" #define RESET "\x1b[0m"
#define RED "\x1b[0;31m" #define RED(S) "\x1b[0;31m" S RESET
#define GREEN "\x1b[0;32m" #define GREEN(S) "\x1b[0;32m" S RESET
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) 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) 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; g_current_test_fail = false;
it->m_fn(); it->m_fn();
if (!g_current_test_fail) 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; pass += 1;
} }
else else
{ {
asl::eprint(RED "[ FAILED ]" RESET " {}\n", it->m_case_name); asl::eprint(RED("[ FAILED ]") " {}\n", it->m_case_name);
fail += 1; fail += 1;
} }
} }
asl::eprint(GREEN "[----------]" RESET " {} test(s) run\n", fail + pass); asl::eprint(GREEN("[----------]") " {} test(s) run\n", fail + pass);
if (fail == 0) if (fail == 0)
{ {
asl::eprint(GREEN "[ PASSED ]" RESET " Good job!\n"); asl::eprint(GREEN("[ PASSED ]") " Good job!\n");
} }
else else
{ {
asl::eprint(RED "[ FAILED ]" RESET " {} test(s) failed\n", fail); asl::eprint(RED("[ FAILED ]") " {} test(s) failed\n", fail);
} }
return 0; return fail;
} }

View File

@ -36,3 +36,11 @@ struct Test
#CASE, \ #CASE, \
asl_test_fn_##CASE); \ asl_test_fn_##CASE); \
void 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__); }

View File

@ -1,4 +1,5 @@
#include "asl/format.hpp" #include "asl/format.hpp"
#include "asl/testing/testing.hpp"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -33,74 +34,73 @@ public:
} }
}; };
int main2() ASL_TEST(Format)
{ {
StringSink sink; 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!"); asl::format(&sink, "Hello, world!");
assert(strcmp(sink.cstr(), "Hello, world!") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, world!") == 0);
sink.reset(); sink.reset();
asl::format(&sink, ""); asl::format(&sink, "");
assert(strcmp(sink.cstr(), "") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "Hello, {}!", "world"); asl::format(&sink, "Hello, {}!", "world");
assert(strcmp(sink.cstr(), "Hello, world!") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, world!") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "Hello, {}! {}", "world"); asl::format(&sink, "Hello, {}! {}", "world");
assert(strcmp(sink.cstr(), "Hello, world! <ERROR>") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, world! <ERROR>") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "Hello, pup!", "world"); asl::format(&sink, "Hello, pup!", "world");
assert(strcmp(sink.cstr(), "Hello, pup!") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, pup!") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "{}", "CHEESE"); asl::format(&sink, "{}", "CHEESE");
assert(strcmp(sink.cstr(), "CHEESE") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "CHEESE") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "{ ", "CHEESE"); asl::format(&sink, "{ ", "CHEESE");
assert(strcmp(sink.cstr(), "<ERROR> ") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "<ERROR> ") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "{", "CHEESE"); asl::format(&sink, "{", "CHEESE");
assert(strcmp(sink.cstr(), "<ERROR>") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "<ERROR>") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "a{{b"); asl::format(&sink, "a{{b");
assert(strcmp(sink.cstr(), "a{b") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "a{b") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "{{{}}} }", "CHEESE"); asl::format(&sink, "{{{}}} }", "CHEESE");
assert(strcmp(sink.cstr(), "{CHEESE} }") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "{CHEESE} }") == 0);
sink.reset(); sink.reset();
asl::format(&sink, "{} {} {}", 0, 1, 2); 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(); sink.reset();
asl::format(&sink, "{} {} {}", 10, 11, 12); 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(); sink.reset();
asl::format(&sink, "{} {} {}", 100, 101, 102); 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(); sink.reset();
asl::format(&sink, "{} {} {}", 1000, 1001, 1002); 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(); sink.reset();
asl::format(&sink, "{} {} {} {}", -1, -23, -456, -7890); 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(); sink.reset();
asl::format(&sink, "{} {}", true, false); asl::format(&sink, "{} {}", true, false);
assert(strcmp(sink.cstr(), "true false") == 0); ASL_TEST_ASSERT(strcmp(sink.cstr(), "true false") == 0);
return 0;
} }

View File

@ -24,35 +24,90 @@ static_assert(asl::move_assignable<asl::option<int>>);
static_assert(asl::move_assignable<asl::option<CopyAssignable>>); static_assert(asl::move_assignable<asl::option<CopyAssignable>>);
static_assert(!asl::move_assignable<asl::option<NonMoveAssignable>>); static_assert(!asl::move_assignable<asl::option<NonMoveAssignable>>);
ASL_TEST(Option_cheese) ASL_TEST(make_null)
{ {
asl::option<int> a; asl::option<int> a;
asl::option<int> b; asl::option<int> 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<int> a; asl::option<int> a = 48;
asl::option<int> b;
a = ASL_MOVE(b); ASL_TEST_EXPECT(a.has_value());
} }
ASL_TEST(Option_cheese3) ASL_TEST(reset)
{ {
asl::option<int> a; asl::option<int> b = 48;
asl::option<int> b; ASL_TEST_EXPECT(b.has_value());
a = ASL_MOVE(b); b.reset();
asl::testing::report_failure("OH NO", __FILE__, __LINE__); ASL_TEST_EXPECT(!b.has_value());
} }
ASL_TEST(Option_cheese4) ASL_TEST(call_destructor)
{ {
asl::option<int> a; bool destroyed = false;
asl::option<int> b;
a = ASL_MOVE(b); {
DestructorObserver obs(&destroyed);
asl::option<DestructorObserver> opt(ASL_MOVE(obs));
ASL_TEST_EXPECT(!destroyed);
asl::option<DestructorObserver> opt2 = ASL_MOVE(opt);
ASL_TEST_EXPECT(!destroyed);
}
ASL_TEST_EXPECT(destroyed);
}
ASL_TEST(call_destructor_on_reset)
{
bool destroyed = false;
asl::option<DestructorObserver> opt(&destroyed);
ASL_TEST_EXPECT(!destroyed);
opt.reset();
ASL_TEST_EXPECT(destroyed);
}
ASL_TEST(value)
{
asl::option<int> a = 1;
asl::option<int> b = 2;
asl::option<int> c = a;
ASL_TEST_EXPECT(a.value() == 1);
ASL_TEST_EXPECT(b.value() == 2);
ASL_TEST_EXPECT(c.value() == 1);
c = b;
ASL_TEST_EXPECT(c.value() == 2);
}
ASL_TEST(value_move)
{
bool destroyed = false;
asl::option<DestructorObserver> 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);
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "asl/utility.hpp"
struct DefaultConstructible { DefaultConstructible() {} }; struct DefaultConstructible { DefaultConstructible() {} };
struct TriviallyDefaultConstructible { TriviallyDefaultConstructible() = default; }; struct TriviallyDefaultConstructible { TriviallyDefaultConstructible() = default; };
struct NonDefaultConstructible { NonDefaultConstructible() = delete; }; struct NonDefaultConstructible { NonDefaultConstructible() = delete; };
@ -23,3 +25,31 @@ struct NonMoveAssignable { NonMoveAssignable& operator=(NonMoveAssignable&&) = d
struct TriviallyDestructible { ~TriviallyDestructible() = default; }; struct TriviallyDestructible { ~TriviallyDestructible() = default; };
struct HasDestructor { ~HasDestructor() {} }; 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;
}
}
};