// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2018 Red Hat * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. * */ #include "common/async/completion.h" #include #include #include namespace ceph::async { using boost::system::error_code; struct move_only { move_only() = default; move_only(move_only&&) = default; move_only& operator=(move_only&&) = default; move_only(const move_only&) = delete; move_only& operator=(const move_only&) = delete; }; TEST(AsyncCompletion, BindHandler) { auto h1 = [] (int i, char c) {}; auto b1 = bind_handler(std::move(h1), 5, 'a'); b1(); const auto& c1 = b1; c1(); std::move(b1)(); // move-only types can be forwarded with 'operator() &&' auto h2 = [] (move_only&& m) {}; auto b2 = bind_handler(std::move(h2), move_only{}); std::move(b2)(); // references bound with std::ref() can be passed to all operator() overloads auto h3 = [] (int& c) { c++; }; int count = 0; auto b3 = bind_handler(std::move(h3), std::ref(count)); EXPECT_EQ(0, count); b3(); EXPECT_EQ(1, count); const auto& c3 = b3; c3(); EXPECT_EQ(2, count); std::move(b3)(); EXPECT_EQ(3, count); } TEST(AsyncCompletion, ForwardHandler) { // move-only types can be forwarded with 'operator() &' auto h = [] (move_only&& m) {}; auto b = bind_handler(std::move(h), move_only{}); auto f = forward_handler(std::move(b)); f(); } TEST(AsyncCompletion, MoveOnly) { boost::asio::io_context context; auto ex1 = context.get_executor(); std::optional ec1, ec2, ec3; std::optional arg3; { // move-only user data using Completion = Completion; auto c = Completion::create(ex1, [&ec1] (error_code ec) { ec1 = ec; }); Completion::post(std::move(c), boost::asio::error::operation_aborted); EXPECT_FALSE(ec1); } { // move-only handler using Completion = Completion; auto c = Completion::create(ex1, [&ec2, m=move_only{}] (error_code ec) { static_cast(m); ec2 = ec; }); Completion::post(std::move(c), boost::asio::error::operation_aborted); EXPECT_FALSE(ec2); } { // move-only arg in signature using Completion = Completion; auto c = Completion::create(ex1, [&] (error_code ec, move_only m) { ec3 = ec; arg3 = std::move(m); }); Completion::post(std::move(c), boost::asio::error::operation_aborted, move_only{}); EXPECT_FALSE(ec3); } context.poll(); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(ec1); EXPECT_EQ(boost::asio::error::operation_aborted, *ec1); ASSERT_TRUE(ec2); EXPECT_EQ(boost::asio::error::operation_aborted, *ec2); ASSERT_TRUE(ec3); EXPECT_EQ(boost::asio::error::operation_aborted, *ec3); ASSERT_TRUE(arg3); } TEST(AsyncCompletion, VoidCompletion) { boost::asio::io_context context; auto ex1 = context.get_executor(); using Completion = Completion; std::optional ec1; auto c = Completion::create(ex1, [&ec1] (error_code ec) { ec1 = ec; }); Completion::post(std::move(c), boost::asio::error::operation_aborted); EXPECT_FALSE(ec1); context.poll(); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(ec1); EXPECT_EQ(boost::asio::error::operation_aborted, *ec1); } TEST(AsyncCompletion, CompletionList) { boost::asio::io_context context; auto ex1 = context.get_executor(); using T = AsBase>; using Completion = Completion; boost::intrusive::list completions; int completed = 0; for (int i = 0; i < 3; i++) { auto c = Completion::create(ex1, [&] { completed++; }); completions.push_back(*c.release()); } completions.clear_and_dispose([] (Completion *c) { Completion::post(std::unique_ptr{c}); }); EXPECT_EQ(0, completed); context.poll(); EXPECT_TRUE(context.stopped()); EXPECT_EQ(3, completed); } TEST(AsyncCompletion, CompletionPair) { boost::asio::io_context context; auto ex1 = context.get_executor(); using T = std::pair; using Completion = Completion; std::optional t; auto c = Completion::create(ex1, [&] (int first, std::string second) { t = T{first, std::move(second)}; }, 2, "hello"); auto data = std::move(c->user_data); Completion::post(std::move(c), data.first, std::move(data.second)); EXPECT_FALSE(t); context.poll(); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(t); EXPECT_EQ(2, t->first); EXPECT_EQ("hello", t->second); } TEST(AsyncCompletion, CompletionReference) { boost::asio::io_context context; auto ex1 = context.get_executor(); using Completion = Completion; auto c = Completion::create(ex1, [] (int& i) { ++i; }); int i = 42; Completion::post(std::move(c), std::ref(i)); EXPECT_EQ(42, i); context.poll(); EXPECT_TRUE(context.stopped()); EXPECT_EQ(43, i); } struct throws_on_move { throws_on_move() = default; throws_on_move(throws_on_move&&) { throw std::runtime_error("oops"); } throws_on_move& operator=(throws_on_move&&) { throw std::runtime_error("oops"); } throws_on_move(const throws_on_move&) = default; throws_on_move& operator=(const throws_on_move&) = default; }; TEST(AsyncCompletion, ThrowOnCtor) { boost::asio::io_context context; auto ex1 = context.get_executor(); { using Completion = Completion; // throw on Handler move construction EXPECT_THROW(Completion::create(ex1, [t=throws_on_move{}] (int& i) { static_cast(t); ++i; }), std::runtime_error); } { using T = throws_on_move; using Completion = Completion; // throw on UserData construction EXPECT_THROW(Completion::create(ex1, [] (int& i) { ++i; }, throws_on_move{}), std::runtime_error); } } TEST(AsyncCompletion, FreeFunctions) { boost::asio::io_context context; auto ex1 = context.get_executor(); auto c1 = create_completion(ex1, [] {}); post(std::move(c1)); auto c2 = create_completion(ex1, [] (int) {}, 5); defer(std::move(c2), c2->user_data); context.poll(); EXPECT_TRUE(context.stopped()); } } // namespace ceph::async