|
33 | 33 | #include <test_common/type_helpers.hpp> |
34 | 34 |
|
35 | 35 | #include <array> |
| 36 | +#include <exception> |
| 37 | +#include <optional> |
36 | 38 |
|
37 | 39 | using namespace std::chrono_literals; |
38 | 40 | using namespace exec; |
@@ -227,6 +229,91 @@ namespace |
227 | 229 | auto op = subscribe(std::move(merged), null_receiver{}); |
228 | 230 | } |
229 | 231 |
|
| 232 | + template <class Error> |
| 233 | + struct error_sequence |
| 234 | + { |
| 235 | + using sender_concept = sequence_sender_tag; |
| 236 | + using item_types = exec::item_types<>; |
| 237 | + using completion_signatures = ex::completion_signatures<ex::set_error_t(Error)>; |
| 238 | + |
| 239 | + template <ex::receiver Receiver> |
| 240 | + struct operation |
| 241 | + { |
| 242 | + void start() & noexcept |
| 243 | + { |
| 244 | + ex::set_error(static_cast<Receiver&&>(receiver_), static_cast<Error&&>(error_)); |
| 245 | + } |
| 246 | + |
| 247 | + Receiver receiver_; |
| 248 | + Error error_; |
| 249 | + }; |
| 250 | + |
| 251 | + template <ex::receiver Receiver> |
| 252 | + auto subscribe(Receiver receiver) && noexcept -> operation<Receiver> |
| 253 | + { |
| 254 | + return {static_cast<Receiver&&>(receiver), static_cast<Error&&>(error_)}; |
| 255 | + } |
| 256 | + |
| 257 | + Error error_; |
| 258 | + }; |
| 259 | + |
| 260 | + struct error_state |
| 261 | + { |
| 262 | + std::optional<int> nested_error_{}; |
| 263 | + std::exception_ptr outer_error_{}; |
| 264 | + bool completed_ = false; |
| 265 | + bool stopped_ = false; |
| 266 | + }; |
| 267 | + |
| 268 | + struct record_error_receiver |
| 269 | + { |
| 270 | + using receiver_concept = ex::receiver_tag; |
| 271 | + |
| 272 | + template <ex::sender Item> |
| 273 | + auto set_next(Item&& item) & |
| 274 | + { |
| 275 | + auto record_error = [state = state_]<class Error>(Error&& error) noexcept |
| 276 | + { |
| 277 | + if constexpr (std::same_as<std::decay_t<Error>, int>) |
| 278 | + { |
| 279 | + state->nested_error_ = static_cast<Error&&>(error); |
| 280 | + } |
| 281 | + }; |
| 282 | + return ex::upon_stopped(ex::upon_error(static_cast<Item&&>(item), record_error), |
| 283 | + []() noexcept {}); |
| 284 | + } |
| 285 | + |
| 286 | + void set_value() noexcept |
| 287 | + { |
| 288 | + state_->completed_ = true; |
| 289 | + } |
| 290 | + |
| 291 | + template <class Error> |
| 292 | + void set_error(Error&& error) noexcept |
| 293 | + { |
| 294 | + if constexpr (std::same_as<std::decay_t<Error>, int>) |
| 295 | + { |
| 296 | + state_->nested_error_ = static_cast<Error&&>(error); |
| 297 | + } |
| 298 | + else if constexpr (std::same_as<std::decay_t<Error>, std::exception_ptr>) |
| 299 | + { |
| 300 | + state_->outer_error_ = static_cast<Error&&>(error); |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | + void set_stopped() noexcept |
| 305 | + { |
| 306 | + state_->stopped_ = true; |
| 307 | + } |
| 308 | + |
| 309 | + auto get_env() const noexcept -> ex::env<> |
| 310 | + { |
| 311 | + return {}; |
| 312 | + } |
| 313 | + |
| 314 | + error_state* state_; |
| 315 | + }; |
| 316 | + |
230 | 317 | TEST_CASE("merge_each - merge two sequence senders of no elements", |
231 | 318 | "[sequence_senders][merge_each][empty_sequence]") |
232 | 319 | { |
@@ -368,31 +455,59 @@ namespace |
368 | 455 | CHECK(v.has_value() == true); |
369 | 456 | } |
370 | 457 |
|
371 | | -// TODO - fix problem with stopping |
372 | | -#if 0 |
373 | | - TEST_CASE( |
374 | | - "merge_each - merge_each sender stops when a nested sequence fails", |
375 | | - "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { |
| 458 | + TEST_CASE("merge_each - preserves errors from nested value senders", |
| 459 | + "[sequence_senders][merge_each]") |
| 460 | + { |
| 461 | + error_state state{}; |
| 462 | + auto merged = merge_each(ex::just(ex::just_error(42))); |
| 463 | + auto op = subscribe(std::move(merged), record_error_receiver{&state}); |
| 464 | + |
| 465 | + ex::start(op); |
| 466 | + |
| 467 | + CHECK(state.nested_error_ == 42); |
| 468 | + CHECK(state.completed_); |
| 469 | + CHECK_FALSE(state.stopped_); |
| 470 | + } |
| 471 | + |
| 472 | + TEST_CASE("merge_each - preserves errors from nested sequences", "[sequence_senders][merge_each]") |
| 473 | + { |
| 474 | + error_state state{}; |
| 475 | + auto nested_sequence = error_sequence<int>{42}; |
| 476 | + auto merged = merge_each(ex::just(std::move(nested_sequence))); |
| 477 | + auto op = subscribe(std::move(merged), record_error_receiver{&state}); |
376 | 478 |
|
377 | | - auto sequences = merge( |
378 | | - log_start(range(100, 120), "range 100-120"), |
379 | | - ex::just(emits_error(std::runtime_error{"failed sequence "})), |
380 | | - log_start(range(200, 220), "range 200-220") |
381 | | - ); |
| 479 | + ex::start(op); |
382 | 480 |
|
383 | | - [[maybe_unused]] auto merged = merge_each(std::move(sequences)); |
| 481 | + CHECK(state.nested_error_ == 42); |
| 482 | + CHECK(state.completed_); |
| 483 | + CHECK_FALSE(state.stopped_); |
| 484 | + } |
384 | 485 |
|
385 | | - int count = 0; |
| 486 | +#if !STDEXEC_NO_STDCPP_EXCEPTIONS() |
| 487 | + TEST_CASE("merge_each - preserves errors from the outer sequence", |
| 488 | + "[sequence_senders][merge_each]") |
| 489 | + { |
| 490 | + error_state state{}; |
| 491 | + auto merged = merge_each(error_sequence<int>{42}); |
| 492 | + auto op = subscribe(std::move(merged), record_error_receiver{&state}); |
386 | 493 |
|
387 | | - auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ |
388 | | - ++count; |
389 | | - UNSCOPED_INFO("item: " << x |
390 | | - << ", on thread id: " << std::this_thread::get_id()); |
391 | | - }))); |
| 494 | + ex::start(op); |
392 | 495 |
|
393 | | - CHECK(count == 20); |
394 | | - CHECK(v.has_value() == false); |
| 496 | + CHECK(state.outer_error_ != nullptr); |
| 497 | + bool caught = false; |
| 498 | + try |
| 499 | + { |
| 500 | + std::rethrow_exception(state.outer_error_); |
| 501 | + } |
| 502 | + catch (int error) |
| 503 | + { |
| 504 | + caught = true; |
| 505 | + CHECK(error == 42); |
| 506 | + } |
| 507 | + CHECK(caught); |
| 508 | + CHECK_FALSE(state.completed_); |
| 509 | + CHECK_FALSE(state.stopped_); |
395 | 510 | } |
396 | | -#endif // 0 |
| 511 | +#endif |
397 | 512 |
|
398 | 513 | } // namespace |
0 commit comments