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-global-constructors
test --test_output=all
test --test_output=errors

View File

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

View File

@ -39,7 +39,7 @@ 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 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; }

View File

@ -40,6 +40,13 @@ public:
constexpr option() = default;
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>
{
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<typename T>
option(T) -> option<T>;
} // 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("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;
}

View File

@ -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__); }

View File

@ -1,4 +1,5 @@
#include "asl/format.hpp"
#include "asl/testing/testing.hpp"
#include <cstdlib>
#include <cstring>
@ -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! <ERROR>") == 0);
ASL_TEST_ASSERT(strcmp(sink.cstr(), "Hello, world! <ERROR>") == 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(), "<ERROR> ") == 0);
ASL_TEST_ASSERT(strcmp(sink.cstr(), "<ERROR> ") == 0);
sink.reset();
asl::format(&sink, "{", "CHEESE");
assert(strcmp(sink.cstr(), "<ERROR>") == 0);
ASL_TEST_ASSERT(strcmp(sink.cstr(), "<ERROR>") == 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);
}

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<NonMoveAssignable>>);
ASL_TEST(Option_cheese)
ASL_TEST(make_null)
{
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> b;
asl::option<int> a = 48;
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;
asl::option<int> b = 48;
ASL_TEST_EXPECT(b.has_value());
a = ASL_MOVE(b);
asl::testing::report_failure("OH NO", __FILE__, __LINE__);
b.reset();
ASL_TEST_EXPECT(!b.has_value());
}
ASL_TEST(Option_cheese4)
ASL_TEST(call_destructor)
{
asl::option<int> a;
asl::option<int> b;
bool destroyed = false;
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
#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;
}
}
};