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
10 changes: 3 additions & 7 deletions include/chain/segment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class segment {

public:
/*
An apply operation may inject additional arguments into the segment. The plan is that the
receiver will get sent to apply and this is how cancellation tokens can be injected into an
The invole operation may inject additional arguments into the segment. The plan is that the
receiver will get sent to invoke and this is how cancellation tokens can be injected into an
operation. Something like `with_cancellation`.

This feature is also used for the `then` operation where the resolve future is injected into
Expand All @@ -44,10 +44,6 @@ class segment {
explicit segment(type<Injects>, Applicator&& apply, Fs&&... functions)
: _functions{std::move(functions)...}, _apply{std::move(apply)} {}

/*
The basic operations should follow those from C++ lambdas, for now default everything.
and see if the compiler gets it correct.
*/
segment(const segment&) = default;
segment(segment&&) noexcept = default;
auto operator=(const segment&) -> segment& = default;
Expand All @@ -61,7 +57,7 @@ class segment {
}

/*
The apply function for a segment always returns void.
The invoke function for a segment always returns void.

Invoke will check the receiver for cancellation -
If not canceled, apply(segment), cancellation is checked before execution of the segment
Expand Down
135 changes: 79 additions & 56 deletions test/segment_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,80 +29,101 @@ struct mock_receiver {

TEST_CASE("Basic segment operations", "[segment]") {
SECTION("simple creation with variadic constructor") {
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); }, []() { return 42; }};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[]() -> int { return 42; }};
(void)sut;
}

SECTION("simple creation with tuple constructor") {
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
std::make_tuple([]() { return 42; })};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
std::make_tuple([]() -> int { return 42; })};
(void)sut;
}

SECTION("creation with multiple functions") {
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[](int x) { return x + 1; }, [](int x) { return x * 2; }};
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[](int x) -> int { return x + 1; }, [](int x) -> int { return x * 2; }};
(void)sut;
}

SECTION("creation with empty function tuple") {
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); }, std::tuple<>{}};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
std::tuple<>{}};
(void)sut;
}
}

TEST_CASE("Segment copy and move semantics", "[segment]") {
SECTION("copy constructor") {
auto original =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); }, []() { return 42; }};
[[maybe_unused]] auto copy{
original}; // Use direct initialization due to explicit constructor
// Both should be valid and independent
auto original = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[]() -> int { return 42; }};
auto copy{original};

auto original_result = std::move(original).result_type_helper();
CHECK(original_result == 42);
auto copy_result = std::move(copy).result_type_helper();
CHECK(copy_result == 42);
}

SECTION("move constructor") {
auto original =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); }, []() { return 42; }};
[[maybe_unused]] auto moved = std::move(original);
// moved should be valid
auto reference = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[]() -> int { return 42; }};

auto original = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[]() -> int { return 42; }};

auto moved = std::move(original);
auto moved_result = std::move(moved).result_type_helper();
CHECK(moved_result == 42);
auto reference_result = std::move(reference).result_type_helper();
CHECK(reference_result == 42);
}

SECTION("segment with move-only types") {
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[m = stlab::move_only(42)]() { return m.member(); }};
auto moved = std::move(sut);
// moved should be valid
auto reference = chain::segment{
chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[m = stlab::move_only(42)]() mutable -> stlab::move_only { return std::move(m); }};
auto original = chain::segment{
chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[m = stlab::move_only(42)]() mutable -> stlab::move_only { return std::move(m); }};

auto moved = std::move(original);
auto moved_result = std::move(moved).result_type_helper();
CHECK(moved_result == stlab::move_only(42));
auto reference_result = std::move(reference).result_type_helper();
CHECK(reference_result == stlab::move_only(42));
}
}

TEST_CASE("Segment result_type_helper", "[segment]") {
SECTION("single function returning int") {
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); }, []() { return 42; }};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[]() -> int { return 42; }};
auto result = std::move(sut).result_type_helper();
CHECK(result == 42);
}

SECTION("function chain with transformations") {
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[](int x) { return x + 1; }, [](int x) { return x * 2; }};
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[](int x) -> int { return x + 1; }, [](int x) -> int { return x * 2; }};
auto result = std::move(sut).result_type_helper(5);
CHECK(result == 12); // (5 + 1) * 2 = 12
}

SECTION("function chain returning string") {
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[](int x) { return x * 2; }, [](int x) { return std::to_string(x); }};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[](int x) -> int { return x * 2; },
[](int x) -> std::string { return std::to_string(x); }};
auto result = std::move(sut).result_type_helper(21);
CHECK(result == "42");
}

SECTION("void returning function") {
auto hit = 0;
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[&hit](int x) { hit = x; }};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[&hit](int x) -> void { hit = x; }};
std::move(sut).result_type_helper(42);
CHECK(hit == 42);
}
Expand All @@ -112,9 +133,9 @@ TEST_CASE("Segment invoke with receiver", "[segment]") {
SECTION("invoke with non-canceled receiver") {
auto receiver = std::make_shared<mock_receiver>();
auto hit = 0;
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f, auto... args) { f(args...); },
[&hit](int x) { hit = x; }};
auto sut = chain::segment{chain::type<std::tuple<>>{},
[](auto f, auto... args) -> void { f(args...); },
[&hit](int x) -> void { hit = x; }};

std::move(sut).invoke(receiver, 42);
CHECK(hit == 42);
Expand All @@ -125,22 +146,22 @@ TEST_CASE("Segment invoke with receiver", "[segment]") {
auto receiver = std::make_shared<mock_receiver>();
receiver->_canceled = true;
auto hit = 0;
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f, auto... args) { f(args...); },
[&hit](int x) { hit = x; }};
auto sut = chain::segment{chain::type<std::tuple<>>{},
[](auto f, auto... args) -> void { f(args...); },
[&hit](int x) -> void { hit = x; }};

std::move(sut).invoke(receiver, 42);
CHECK(hit == 0); // Should not execute
}

SECTION("invoke with exception in segment") {
auto receiver = std::make_shared<mock_receiver>();
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f, auto... args) { f(args...); },
[](int x) {
if (x == 42) throw std::runtime_error("test error");
return x;
}};
auto sut = chain::segment{chain::type<std::tuple<>>{},
[](auto f, auto... args) -> void { f(args...); },
[](int x) -> int {
if (x == 42) throw std::runtime_error("test error");
return x;
}};

std::move(sut).invoke(receiver, 42);
CHECK(receiver->_exception != nullptr);
Expand Down Expand Up @@ -172,10 +193,10 @@ TEST_CASE("Segment invoke with receiver", "[segment]") {
SECTION("invoke with chained functions") {
auto receiver = std::make_shared<mock_receiver>();
auto result = 0;
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f, auto... args) { f(args...); },
[](int x) { return x + 1; }, [](int x) { return x * 2; },
[&result](int x) { result = x; }};
auto sut = chain::segment{
chain::type<std::tuple<>>{}, [](auto f, auto... args) -> void { f(args...); },
[](int x) -> int { return x + 1; }, [](int x) -> int { return x * 2; },
[&result](int x) -> void { result = x; }};

std::move(sut).invoke(receiver, 5);
CHECK(result == 12); // (5 + 1) * 2 = 12
Expand All @@ -185,48 +206,50 @@ TEST_CASE("Segment invoke with receiver", "[segment]") {

TEST_CASE("Segment with injected types", "[segment]") {
SECTION("segment with int injection") {
auto sut = chain::segment{chain::type<std::tuple<int>>{}, [](auto f) { f(); },
[]() { return 42; }};
auto sut = chain::segment{chain::type<std::tuple<int>>{}, [](auto f) -> void { f(); },
[]() -> int { return 42; }};
// Segment should be constructible with injection type
(void)sut;
}

SECTION("segment with multiple injection types") {
auto sut = chain::segment{chain::type<std::tuple<int, std::string>>{}, [](auto f) { f(); },
[]() { return 42; }};
auto sut = chain::segment{chain::type<std::tuple<int, std::string>>{},
[](auto f) -> void { f(); }, []() -> int { return 42; }};
// Segment should be constructible with multiple injection types
(void)sut;
}
}

TEST_CASE("Segment edge cases", "[segment]") {
SECTION("empty segment with no functions") {
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); }, std::tuple<>{}};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
std::tuple<>{}};
// Should be constructible
(void)sut;
}

SECTION("segment with void function") {
auto hit = false;
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[&hit]() { hit = true; }};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[&hit]() -> void { hit = true; }};
std::move(sut).result_type_helper();
CHECK(hit);
}

SECTION("segment with multiple void functions") {
auto hit1 = false;
auto hit2 = false;
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[&hit1]() { hit1 = true; }, [&hit2]() { hit2 = true; }};
auto sut =
chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[&hit1]() -> void { hit1 = true; }, [&hit2]() -> void { hit2 = true; }};
std::move(sut).result_type_helper();
CHECK(hit1);
CHECK(hit2);
}

SECTION("segment with variadic function") {
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) { f(); },
[](auto... args) { return (args + ...); }};
auto sut = chain::segment{chain::type<std::tuple<>>{}, [](auto f) -> void { f(); },
[](auto... args) -> int { return (args + ...); }};
auto result = std::move(sut).result_type_helper(1, 2, 3, 4);
CHECK(result == 10);
}
Expand Down
Loading