Files
scylladb/test/boost/result_utils_test.cc
Piotr Dulikowski 11cb670881 utils: add result utils
Adds a number of utilities for working with boost::outcome::result
combined with exception_container. The utilities are meant to help with
migration of the existing code to use the boost::outcome::result:

- `exception_container_throw_policy` - a NoValuePolicy meant to be used
  as a template parameter for the boost::outcome::result. It protects
  the caller of `result::value()` and `result::error()` methods - if the
  caller wishes to get a value but the result has an error
  (exception_container in our case), the exception in the container will
  be thrown instead. In case it's the other way around,
  boost::outcome::bad_result_access is thrown.
- `result_parallel_for_each` - a version of `parallel_for_each` which is
  aware of results and returns a failed result in case any of the
  parallel invocations return a failed result.
- `result_into_future` - converts a result into a future. If the result
  holds a value, converts it into make_ready_future; if it holds an
  exception, the exception is returned as make_exception_future.
- `then_ok_result` takes a `future<T>` and converts it into
  a `future<result<T>>`.
- `result_wrap` adapts a callable of type `T -> future<result<T>>` and
  returns a callable of type `result<T> -> future<result<T>>`.
2022-02-08 11:08:42 +01:00

128 lines
4.1 KiB
C++

/*
* Copyright (C) 2022-present ScyllaDB
*/
/*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <vector>
#include <stdexcept>
#include "utils/exception_container.hh"
#include "utils/result.hh"
#include <seastar/testing/test_case.hh>
#include <seastar/core/sstring.hh>
#include <seastar/core/map_reduce.hh>
#include <seastar/testing/thread_test_case.hh>
using namespace seastar;
class foo_exception : public std::exception {
public:
const char* what() const noexcept override {
return "foo";
}
};
class bar_exception : public std::exception {
public:
const char* what() const noexcept override {
return "bar";
}
};
using exc_container = utils::exception_container<foo_exception, bar_exception>;
template<typename T = void>
using result = bo::result<T, exc_container,utils::exception_container_throw_policy>;
SEASTAR_TEST_CASE(test_exception_container_throw_policy) {
result<> r_ok = bo::success();
BOOST_REQUIRE_NO_THROW(r_ok.value());
BOOST_REQUIRE_THROW(r_ok.error(), bo::bad_result_access);
result<> r_err_foo = bo::failure(foo_exception());
BOOST_REQUIRE_NO_THROW(r_err_foo.error());
BOOST_REQUIRE_THROW(r_err_foo.value(), foo_exception);
return make_ready_future<>();
}
SEASTAR_THREAD_TEST_CASE(test_result_into_future) {
// T == void
result<> r_ok = bo::success();
auto f_ok = utils::result_into_future(std::move(r_ok));
BOOST_REQUIRE_NO_THROW(f_ok.get());
result<> r_err_foo = bo::failure(foo_exception());
auto f_err_foo = utils::result_into_future(std::move(r_err_foo));
BOOST_REQUIRE_THROW(f_err_foo.get(), foo_exception);
// T != void
result<int> r_ok_int = bo::success();
auto f_ok_int = utils::result_into_future(std::move(r_ok_int));
BOOST_REQUIRE_NO_THROW(f_ok_int.get());
result<int> r_err_foo_int = bo::failure(foo_exception());
auto f_err_foo_int = utils::result_into_future(std::move(r_err_foo_int));
BOOST_REQUIRE_THROW(f_err_foo_int.get(), foo_exception);
}
SEASTAR_THREAD_TEST_CASE(test_then_ok_result) {
auto f_void = utils::then_ok_result<result<>>(make_ready_future<>());
BOOST_REQUIRE_NO_THROW(f_void.get().value());
auto f_int = utils::then_ok_result<result<int>>(make_ready_future<int>(123));
BOOST_REQUIRE_EQUAL(f_int.get().value(), 123);
}
SEASTAR_THREAD_TEST_CASE(test_result_wrap) {
int run_count = 0;
// T == void
auto fun_void = utils::result_wrap([&run_count] {
++run_count;
return result<>(bo::success());
});
BOOST_REQUIRE_NO_THROW(fun_void(result<>(bo::success())).get().value());
BOOST_REQUIRE_EQUAL(run_count, 1);
BOOST_REQUIRE_THROW(fun_void(result<>(bo::failure(foo_exception()))).get().value(), foo_exception);
BOOST_REQUIRE_EQUAL(run_count, 1);
// T != void
auto fun_int = utils::result_wrap([&run_count] (int i) {
++run_count;
return result<int>(bo::success(i));
});
BOOST_REQUIRE_EQUAL(fun_int(result<int>(bo::success(123))).get().value(), 123);
BOOST_REQUIRE_EQUAL(run_count, 2);
BOOST_REQUIRE_THROW(fun_int(result<int>(bo::failure(foo_exception()))).get().value(), foo_exception);
BOOST_REQUIRE_EQUAL(run_count, 2);
}
SEASTAR_THREAD_TEST_CASE(test_result_parallel_for_each) {
auto reduce = [] (auto... params) {
std::vector<result<>> v;
(v.push_back(std::move(params)), ...);
utils::result_parallel_for_each<result<>>(std::move(v), [] (result<>& r) {
return make_ready_future<result<>>(std::move(r));
}).get().value(); // <- trying to access the value throws in case of error
};
auto foo_exc = [] () { return result<>(bo::failure(foo_exception())); };
auto bar_exc = [] () { return result<>(bo::failure(bar_exception())); };
BOOST_REQUIRE_NO_THROW(reduce(bo::success(), bo::success()));
BOOST_REQUIRE_THROW(reduce(foo_exc(), bo::success()), foo_exception);
BOOST_REQUIRE_THROW(reduce(bo::success(), foo_exc()), foo_exception);
BOOST_REQUIRE_THROW(reduce(foo_exc(), bar_exc()), foo_exception);
BOOST_REQUIRE_THROW(reduce(bar_exc(), foo_exc()), bar_exception);
}