Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion include/boost/capy/io/any_buffer_sink.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,10 @@ any_buffer_sink::any_buffer_sink(S s)
bool committed = false;
~guard() {
if(!committed && self->storage_) {
self->vt_->destroy(self->sink_);
// sink_ is null if the sink move-ctor threw before
// the placement-new assigned it.
if(self->sink_)
self->vt_->destroy(self->sink_);
::operator delete(self->storage_);
self->storage_ = nullptr;
self->sink_ = nullptr;
Expand Down
5 changes: 4 additions & 1 deletion include/boost/capy/io/any_buffer_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,10 @@ any_buffer_source::any_buffer_source(S s)
bool committed = false;
~guard() {
if(!committed && self->storage_) {
self->vt_->destroy(self->source_);
// source_ is null if the source move-ctor threw before
// the placement-new assigned it.
if(self->source_)
self->vt_->destroy(self->source_);
::operator delete(self->storage_);
self->storage_ = nullptr;
self->source_ = nullptr;
Expand Down
5 changes: 4 additions & 1 deletion include/boost/capy/io/any_read_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,10 @@ any_read_source::any_read_source(S s)
bool committed = false;
~guard() {
if(!committed && self->storage_) {
self->vt_->destroy(self->source_);
// source_ is null if the source move-ctor threw before
// the placement-new assigned it.
if(self->source_)
self->vt_->destroy(self->source_);
::operator delete(self->storage_);
self->storage_ = nullptr;
self->source_ = nullptr;
Expand Down
5 changes: 4 additions & 1 deletion include/boost/capy/io/any_read_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,10 @@ any_read_stream::any_read_stream(S s)
bool committed = false;
~guard() {
if(!committed && self->storage_) {
self->vt_->destroy(self->stream_);
// stream_ is null if the stream move-ctor threw before
// the placement-new assigned it.
if(self->stream_)
self->vt_->destroy(self->stream_);
::operator delete(self->storage_);
self->storage_ = nullptr;
self->stream_ = nullptr;
Expand Down
5 changes: 4 additions & 1 deletion include/boost/capy/io/any_write_sink.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,10 @@ any_write_sink::any_write_sink(S s)
bool committed = false;
~guard() {
if(!committed && self->storage_) {
self->vt_->destroy(self->sink_);
// sink_ is null if the sink move-ctor threw before
// the placement-new assigned it.
if(self->sink_)
self->vt_->destroy(self->sink_);
::operator delete(self->storage_);
self->storage_ = nullptr;
self->sink_ = nullptr;
Expand Down
5 changes: 4 additions & 1 deletion include/boost/capy/io/any_write_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,10 @@ any_write_stream::any_write_stream(S s)
bool committed = false;
~guard() {
if(!committed && self->storage_) {
self->vt_->destroy(self->stream_);
// stream_ is null if the stream move-ctor threw before
// the placement-new assigned it.
if(self->stream_)
self->vt_->destroy(self->stream_);
::operator delete(self->storage_);
self->storage_ = nullptr;
self->stream_ = nullptr;
Expand Down
22 changes: 22 additions & 0 deletions test/unit/buffers/buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,17 @@ struct buffer_test
std::span<const_buffer const> bs(&b[0], 3);
test::check_sequence(bs, "123456789");
}

// operator+= advances and clamps to size()
{
char const* p = "12345";
const_buffer b(p, 5);
b += 2;
BOOST_TEST_EQ(b.data(), p + 2);
BOOST_TEST_EQ(b.size(), 3);
b += 100; // over-advance is clamped
BOOST_TEST_EQ(b.size(), 0);
}
}

void testMutableBuffer()
Expand Down Expand Up @@ -442,6 +453,17 @@ struct buffer_test
std::span<mutable_buffer const> bs(&b[0], 3);
test::check_sequence(bs, "123456789");
}

// operator+= advances and clamps to size()
{
char p[6] = "12345";
mutable_buffer b(p, 5);
b += 2;
BOOST_TEST_EQ(b.data(), p + 2);
BOOST_TEST_EQ(b.size(), 3);
b += 100; // over-advance is clamped
BOOST_TEST_EQ(b.size(), 0);
}
}

void testSize()
Expand Down
45 changes: 45 additions & 0 deletions test/unit/concept/decomposes_to.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <tuple>
#include <utility>

#include "test_suite.hpp"

namespace boost {
namespace capy {

Expand Down Expand Up @@ -283,5 +285,48 @@ struct bad_aw
};
#endif

// A type whose awaiter is obtained via a free operator co_await.
struct mock_free_co_await { };

inline mock_int_awaitable
operator co_await(mock_free_co_await) noexcept
{
return {};
}

// The static_asserts above cover decomposes_to at compile time; this
// suite drives get_awaiter at runtime, which otherwise appears only in
// the unevaluated operand of awaitable_return_t.
class decomposes_to_test
{
public:
void
run()
{
// No operator co_await: get_awaiter returns the value itself.
{
mock_int_awaitable a;
auto aw = detail::get_awaiter(a);
BOOST_TEST(aw.await_ready());
}

// Member operator co_await.
{
mock_with_co_await_op a;
auto aw = detail::get_awaiter(a);
BOOST_TEST(aw.await_ready());
}

// Free operator co_await.
{
mock_free_co_await a;
auto aw = detail::get_awaiter(a);
BOOST_TEST(aw.await_ready());
}
}
};

TEST_SUITE(decomposes_to_test, "boost.capy.concept.decomposes_to");

} // namespace capy
} // namespace boost
22 changes: 22 additions & 0 deletions test/unit/cond.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ class cond_test
auto ec = make_error_code(error::eof);
BOOST_TEST(!(ec == cond::not_found));
}

// Remaining messages, including the default branch.
auto const ecnd = make_error_condition(cond::eof);
auto const& cat = ecnd.category();
BOOST_TEST(
cat.message(static_cast<int>(cond::stream_truncated)) ==
"stream truncated");
BOOST_TEST(
cat.message(static_cast<int>(cond::timeout)) ==
"operation timed out");
BOOST_TEST(cat.message(9999) == "unknown");

// Equivalence: stream_truncated and timeout.
BOOST_TEST(make_error_code(error::stream_truncated) ==
cond::stream_truncated);
BOOST_TEST(make_error_code(error::timeout) == cond::timeout);

// Out-of-range condition is equivalent to nothing.
{
auto ec = make_error_code(error::eof);
BOOST_TEST(! cat.equivalent(ec, 9999));
}
}
};

Expand Down
87 changes: 87 additions & 0 deletions test/unit/detail/await_suspend_helper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// Copyright (c) 2026 Steve Gerbino
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/capy
//

// Test that header file is self-contained.
#include <boost/capy/detail/await_suspend_helper.hpp>

#include <boost/capy/ex/io_env.hpp>

#include <coroutine>

#include "test_suite.hpp"

namespace boost {
namespace capy {
namespace detail {

class await_suspend_helper_test
{
// await_suspend returning void: caller suspends unconditionally.
struct void_awaitable
{
bool suspended = false;
void await_suspend(std::coroutine_handle<>, io_env const*)
{
suspended = true;
}
};

// await_suspend returning bool: true suspends, false resumes.
struct bool_awaitable
{
bool value;
bool await_suspend(std::coroutine_handle<>, io_env const*)
{
return value;
}
};

// await_suspend returning a handle: symmetric transfer to it.
struct handle_awaitable
{
std::coroutine_handle<> next;
std::coroutine_handle<>
await_suspend(std::coroutine_handle<>, io_env const*)
{
return next;
}
};

public:
void
run()
{
auto const h = std::noop_coroutine();

// void -> noop_coroutine, and the awaitable was invoked.
void_awaitable va;
BOOST_TEST(call_await_suspend(&va, h, nullptr) == h);
BOOST_TEST(va.suspended);

// bool true -> noop_coroutine (stay suspended).
bool_awaitable bt{true};
BOOST_TEST(call_await_suspend(&bt, h, nullptr) == h);

// bool false -> the original handle (resume).
bool_awaitable bf{false};
BOOST_TEST(call_await_suspend(&bf, h, nullptr) == h);

// handle -> the returned handle.
handle_awaitable ha{h};
BOOST_TEST(call_await_suspend(&ha, h, nullptr) == h);
}
};

TEST_SUITE(
await_suspend_helper_test,
"boost.capy.detail.await_suspend_helper");

} // detail
} // capy
} // boost
97 changes: 97 additions & 0 deletions test/unit/detail/except.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// Copyright (c) 2026 Steve Gerbino
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/capy
//

// Test that header file is self-contained.
#include <boost/capy/detail/except.hpp>

#include <new>
#include <stdexcept>
#include <string>
#include <system_error>
#include <typeinfo>

#include "test_suite.hpp"

namespace boost {
namespace capy {
namespace detail {

class except_test
{
// Run the thrower and return the message carried by the exception.
template<class Ex, class F>
std::string
catch_message(F&& f)
{
try
{
f();
}
catch(Ex const& e)
{
return e.what();
}
return {};
}

public:
// The throw_* helpers are [[noreturn]], so MSVC flags the code
// after each BOOST_TEST_THROWS expression as unreachable.
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4702) // unreachable code after throw
#endif
void
run()
{
BOOST_TEST_THROWS(throw_bad_typeid(), std::bad_typeid);
BOOST_TEST_THROWS(throw_bad_alloc(), std::bad_alloc);

BOOST_TEST_THROWS(throw_invalid_argument(), std::invalid_argument);
BOOST_TEST_THROWS(
throw_invalid_argument("bad"), std::invalid_argument);
BOOST_TEST(
catch_message<std::invalid_argument>(
[] { throw_invalid_argument("bad"); }) == "bad");

BOOST_TEST_THROWS(throw_length_error(), std::length_error);
BOOST_TEST_THROWS(throw_length_error("too long"), std::length_error);
BOOST_TEST(
catch_message<std::length_error>(
[] { throw_length_error("too long"); }) == "too long");

BOOST_TEST_THROWS(throw_logic_error(), std::logic_error);
BOOST_TEST_THROWS(throw_out_of_range(), std::out_of_range);

BOOST_TEST_THROWS(throw_runtime_error("boom"), std::runtime_error);
BOOST_TEST(
catch_message<std::runtime_error>(
[] { throw_runtime_error("boom"); }) == "boom");

auto const ec = std::make_error_code(std::errc::invalid_argument);
BOOST_TEST_THROWS(throw_system_error(ec), std::system_error);
try
{
throw_system_error(ec);
}
catch(std::system_error const& e)
{
BOOST_TEST(e.code() == ec);
}
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
};

TEST_SUITE(except_test, "boost.capy.detail.except");

} // detail
} // capy
} // boost
Loading
Loading