Unverified Commit c677bf5b authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1520

Implement new fixture for flow tests
parents c3031dd3 f0bcdcbd
...@@ -48,6 +48,10 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -48,6 +48,10 @@ is based on [Keep a Changelog](https://keepachangelog.com).
malformed output. malformed output.
- Fix handling of WebSocket frames that are exactly on the 65535 byte limit. - Fix handling of WebSocket frames that are exactly on the 65535 byte limit.
- Fix crash when using a fallback value for optional values (#1427). - Fix crash when using a fallback value for optional values (#1427).
- Using `take(0)` on an observable now properly creates an observable that calls
`on_complete` on its subscriber on the first activity of the source
observable. Previously, the created observable would never reach its threshold
and attempt to buffer all values indefinitely.
## [0.19.2] - 2023-06-13 ## [0.19.2] - 2023-06-13
......
...@@ -150,6 +150,7 @@ caf_add_component( ...@@ -150,6 +150,7 @@ caf_add_component(
caf/execution_unit.cpp caf/execution_unit.cpp
caf/flow/coordinated.cpp caf/flow/coordinated.cpp
caf/flow/coordinator.cpp caf/flow/coordinator.cpp
caf/flow/observable.test.cpp
caf/flow/observable_builder.cpp caf/flow/observable_builder.cpp
caf/flow/op/interval.cpp caf/flow/op/interval.cpp
caf/flow/scoped_coordinator.cpp caf/flow/scoped_coordinator.cpp
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/flow/observable.hpp"
#include "caf/test/caf_test_main.hpp"
#include "caf/test/fixture/flow.hpp"
#include "caf/test/nil.hpp"
#include "caf/test/test.hpp"
using caf::test::nil;
using std::vector;
using namespace caf;
WITH_FIXTURE(test::fixture::flow) {
// Note: we always double the checks for the operator-under-test by calling it
// once on a blueprint and once on an actual observable. This ensures that
// the blueprint and the observable both offer the same functionality.
TEST("skip(n) skips the first n elements in a range of size m") {
SECTION("skip(0) does nothing") {
SECTION("blueprint") {
check_eq(collect(range(1, 0).skip(0)), nil);
check_eq(collect(range(1, 1).skip(0)), vector{1});
check_eq(collect(range(1, 2).skip(0)), vector{1, 2});
check_eq(collect(range(1, 3).skip(0)), vector{1, 2, 3});
}
SECTION("observable") {
check_eq(collect(mat(range(1, 0)).skip(0)), nil);
check_eq(collect(mat(range(1, 1)).skip(0)), vector{1});
check_eq(collect(mat(range(1, 2)).skip(0)), vector{1, 2});
check_eq(collect(mat(range(1, 3)).skip(0)), vector{1, 2, 3});
}
}
SECTION("skip(n) truncates the first n elements if n < m") {
SECTION("blueprint") {
check_eq(collect(range(1, 3).skip(1)), vector{2, 3});
check_eq(collect(range(1, 3).skip(2)), vector{3});
}
SECTION("observable") {
check_eq(collect(mat(range(1, 3)).skip(1)), vector{2, 3});
check_eq(collect(mat(range(1, 3)).skip(2)), vector{3});
}
}
SECTION("skip(n) drops all elements if n >= m") {
SECTION("blueprint") {
check_eq(collect(range(1, 3).skip(3)), nil);
check_eq(collect(range(1, 3).skip(4)), nil);
}
SECTION("observable") {
check_eq(collect(mat(range(1, 3)).skip(3)), nil);
check_eq(collect(mat(range(1, 3)).skip(4)), nil);
}
}
SECTION("skip(n) stops early when the next operator stops early") {
SECTION("blueprint") {
check_eq(collect(range(1, 5).skip(2).take(0)), nil);
check_eq(collect(range(1, 5).skip(2).take(1)), vector{3});
check_eq(collect(range(1, 5).skip(2).take(2)), vector{3, 4});
check_eq(collect(range(1, 5).skip(2).take(3)), vector{3, 4, 5});
check_eq(collect(range(1, 5).skip(2).take(4)), vector{3, 4, 5});
}
SECTION("observable") {
check_eq(collect(mat(range(1, 5)).skip(2).take(0)), nil);
check_eq(collect(mat(range(1, 5)).skip(2).take(1)), vector{3});
check_eq(collect(mat(range(1, 5)).skip(2).take(2)), vector{3, 4});
check_eq(collect(mat(range(1, 5)).skip(2).take(3)), vector{3, 4, 5});
check_eq(collect(mat(range(1, 5)).skip(2).take(4)), vector{3, 4, 5});
}
}
SECTION("skip(n) forwards errors") {
SECTION("blueprint") {
check_eq(collect(obs_error().skip(0)), sec::runtime_error);
check_eq(collect(obs_error().skip(1)), sec::runtime_error);
}
SECTION("observable") {
check_eq(collect(mat(obs_error()).skip(0)), sec::runtime_error);
check_eq(collect(mat(obs_error()).skip(1)), sec::runtime_error);
}
}
}
} // WITH_FIXTURE(test::fixture::flow)
CAF_TEST_MAIN()
...@@ -29,17 +29,17 @@ public: ...@@ -29,17 +29,17 @@ public:
template <class Next, class... Steps> template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) { bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (remaining_ > 0) { if (remaining_ > 0) {
if (next.on_next(item, steps...)) { if (next.on_next(item, steps...))
if (--remaining_ > 0) { --remaining_;
return true; else
} else {
next.on_complete(steps...);
return false; return false;
} }
} if (remaining_ == 0) {
} next.on_complete(steps...);
return false; return false;
} }
return true;
}
template <class Next, class... Steps> template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) { void on_complete(Next& next, Steps&... steps) {
......
...@@ -22,6 +22,8 @@ caf_add_component( ...@@ -22,6 +22,8 @@ caf_add_component(
caf/test/block.cpp caf/test/block.cpp
caf/test/context.cpp caf/test/context.cpp
caf/test/factory.cpp caf/test/factory.cpp
caf/test/fixture/flow.cpp
caf/test/fixture/flow.test.cpp
caf/test/given.cpp caf/test/given.cpp
caf/test/nesting_error.cpp caf/test/nesting_error.cpp
caf/test/registry.cpp caf/test/registry.cpp
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/fixture/flow.hpp"
namespace caf::test::fixture {
void flow::run_flows() {
// TODO: the scoped_coordinator is not the right tool for this job. We need a
// custom coordinator that allows us to control timeouts. For now, this
// is only good enough to get tests running that have no notion of time.
coordinator_->run_some();
}
} // namespace caf::test::fixture
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/core_export.hpp"
#include "caf/error.hpp"
#include "caf/expected.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include <cstddef>
#include <memory>
#include <utility>
namespace caf::test::fixture {
/// A fixture for testing the flow API.
class CAF_CORE_EXPORT flow {
public:
flow() {
coordinator_ = caf::flow::make_scoped_coordinator();
}
/// Returns a new builder for creating observables.
[[nodiscard]] auto make_observable() {
return coordinator_->make_observable();
}
/// Shortcut for creating an observable error via
/// `make_observable<T>().fail<T>(args...)`. When passing an empty parameter
/// pack, the error is constructed from sec::runtime_error.
template <class T = int, class... Args>
[[nodiscard]] auto obs_error(Args&&... args) {
if constexpr (sizeof...(Args) == 0)
return make_observable().fail<T>(make_error(sec::runtime_error));
else
return make_observable().fail<T>(make_error(std::forward<Args>(args)...));
}
/// Shortcut for `make_observable<T>().range(init, num)`.
template <class T>
[[nodiscard]] auto range(T init, size_t num) {
return make_observable().range(init, num);
}
/// Materializes an observable by calling `as_observable` on it.
template <class Observable>
[[nodiscard]] static auto mat(Observable&& x) {
return std::forward<Observable>(x).as_observable();
}
/// Collects all values from an observable into a vector.
template <class Observable>
auto collect(Observable&& observable) {
using value_type = caf::flow::output_type_t<std::decay_t<Observable>>;
using list_type = std::vector<value_type>;
using result_type = expected<list_type>;
auto fin = std::make_shared<bool>(false);
auto err = std::make_shared<error>();
auto values = std::make_shared<list_type>();
std::forward<Observable>(observable)
.do_on_complete([fin] { *fin = true; })
.do_on_error([fin, err](const error& e) {
*fin = true;
*err = std::move(e);
})
.for_each([values](const value_type& value) { //
values->emplace_back(value);
});
run_flows();
if (!*fin) {
// TODO: use fail() on the current runnable instead.
CAF_RAISE_ERROR(std::logic_error, "observable did not complete");
}
if (*err) {
return result_type{std::move(*err)};
}
return result_type{std::move(*values)};
}
/// Runs all flows created by this fixture.
void run_flows();
private:
caf::flow::scoped_coordinator_ptr coordinator_;
};
} // namespace caf::test::fixture
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/fixture/flow.hpp"
#include "caf/test/caf_test_main.hpp"
#include "caf/test/test.hpp"
#include "caf/flow/observable.hpp"
using namespace caf;
WITH_FIXTURE(test::fixture::flow) {
TEST("range() creates a blueprint for a range of numbers") {
auto values = std::make_shared<std::vector<int>>();
range(1, 3).for_each([values](int value) { //
values->emplace_back(value);
});
run_flows();
check_eq(*values, std::vector{1, 2, 3});
}
TEST("collect() eagerly evaluates an observable and returns a vector") {
check_eq(collect(range(1, 0)), std::vector<int>{});
check_eq(collect(range(1, 1)), std::vector{1});
check_eq(collect(range(1, 2)), std::vector{1, 2});
check_eq(collect(range(1, 3)), std::vector{1, 2, 3});
}
} // WITH_FIXTURE(test::fixture::flow)
CAF_TEST_MAIN()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include <string>
#include <vector>
namespace caf::test {
/// A type that can be used to check whether a list is empty.
struct nil_t {};
/// Represents the empty list.
constexpr nil_t nil = nil_t{};
/// @relates nil_t
inline std::string to_string(nil_t) {
return "nil";
}
/// @relates nil_t
template <class T>
bool operator==(const std::vector<T>& values, nil_t) {
return values.empty();
}
/// @relates nil_t
template <class T>
bool operator==(nil_t, const T& values) {
return values == nil;
}
/// @relates nil_t
template <class T>
bool operator!=(const T& values, nil_t) {
return !(values == nil);
}
/// @relates nil_t
template <class T>
bool operator!=(nil_t, const T& values) {
return !(values == nil);
}
} // namespace caf::test
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment