Commit 3e923d48 authored by Nicholas Hollingum's avatar Nicholas Hollingum Committed by Commit Bot

borealis: Infrastructure for state management and state transitions

This infrastructure will hopefully form the basis of the state
management for the borealis system. The important concepts are:
 - State is managed by manager objects, which can be in exactly two
   states (on/off)
 - State is conceptualized with state objects, that are constructed or
   destroyed during state transitions, based on what those transitions
   are defined as.
 - The state manager owns the instance of the state object, so we force
   designs that work a bit like an onion/stack in the sense that
   if state C is a descendant of P then we model this by having P own
   the *state manager* for C (as opposed to having C own/inherit/extend
   P itself).

Bug: b/172178036
Change-Id: I2b065f71972d11e3212509f2f4c569e598ca9c41
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2526865Reviewed-by: default avatarDavid Munro <davidmunro@google.com>
Reviewed-by: default avatarDaniel Ng <danielng@google.com>
Commit-Queue: Nic Hollingum <hollingum@google.com>
Cr-Commit-Position: refs/heads/master@{#830784}
parent 7fc0df60
...@@ -899,6 +899,8 @@ source_set("chromeos") { ...@@ -899,6 +899,8 @@ source_set("chromeos") {
"borealis/borealis_window_manager.h", "borealis/borealis_window_manager.h",
"borealis/infra/described.h", "borealis/infra/described.h",
"borealis/infra/expected.h", "borealis/infra/expected.h",
"borealis/infra/state_manager.h",
"borealis/infra/transition.h",
"browser_context_keyed_service_factories.cc", "browser_context_keyed_service_factories.cc",
"browser_context_keyed_service_factories.h", "browser_context_keyed_service_factories.h",
"camera_detector.cc", "camera_detector.cc",
...@@ -3381,6 +3383,8 @@ source_set("unit_tests") { ...@@ -3381,6 +3383,8 @@ source_set("unit_tests") {
"borealis/borealis_task_unittest.cc", "borealis/borealis_task_unittest.cc",
"borealis/infra/described_unittest.cc", "borealis/infra/described_unittest.cc",
"borealis/infra/expected_unittest.cc", "borealis/infra/expected_unittest.cc",
"borealis/infra/state_manager_unittest.cc",
"borealis/infra/transition_unittest.cc",
"camera_mic/vm_camera_mic_manager_unittest.cc", "camera_mic/vm_camera_mic_manager_unittest.cc",
"cert_provisioning/cert_provisioning_invalidator_unittest.cc", "cert_provisioning/cert_provisioning_invalidator_unittest.cc",
"cert_provisioning/cert_provisioning_platform_keys_helpers_unittest.cc", "cert_provisioning/cert_provisioning_platform_keys_helpers_unittest.cc",
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
// TODO(b/172501195): Make these available outside namespace borealis. // TODO(b/172501195): Make these available outside namespace borealis.
namespace borealis { namespace borealis {
// When signalling results that can succeed or fail, an Expected is used to // When signaling results that can succeed or fail, an Expected is used to
// fomalise those two options either with a |T|, indicating success, or an |E| // formalize those two options either with a |T|, indicating success, or an |E|
// which indicates failure. // which indicates failure.
// //
// This class is a non-drop-in-replacement for the std::expected, as that RFC is // This class is a non-drop-in-replacement for the std::expected, as that RFC is
...@@ -32,18 +32,17 @@ class Expected { ...@@ -32,18 +32,17 @@ class Expected {
// TODO(b/172501195): This implementation is only partial, either complete it // TODO(b/172501195): This implementation is only partial, either complete it
// or replace it with the standard. Until then |T| and |E| should probably be // or replace it with the standard. Until then |T| and |E| should probably be
// copyable. // movable and copy-able respectively.
static_assert(std::is_copy_constructible<T>::value, static_assert(std::is_move_constructible<T>::value,
"Expected's value type must be copy-constructible"); "Expected's value type must be move-constructible");
static_assert(std::is_copy_assignable<T>::value,
"Expected's value type must be copy-assignable");
static_assert(std::is_copy_constructible<E>::value, static_assert(std::is_copy_constructible<E>::value,
"Expected's error type must be copy-constructible"); "Expected's error type must be copy-constructible");
static_assert(std::is_copy_assignable<E>::value, static_assert(std::is_copy_assignable<E>::value,
"Expected's error type must be copy-assignable"); "Expected's error type must be copy-assignable");
// Construct an object with the expected type. // Construct an object with the expected type.
explicit Expected(T value) : storage_(value) {} template <class TT>
explicit Expected(TT&& value) : storage_(std::forward<T>(value)) {}
// Construct an object with the error type. // Construct an object with the error type.
static Expected<T, E> Unexpected(E error) { return Expected(error); } static Expected<T, E> Unexpected(E error) { return Expected(error); }
...@@ -58,11 +57,11 @@ class Expected { ...@@ -58,11 +57,11 @@ class Expected {
// Unsafe access to the underlying value. Use only if you know |this| is in // Unsafe access to the underlying value. Use only if you know |this| is in
// the requested state. // the requested state.
T Value() { return absl::get<T>(storage_); } T& Value() { return absl::get<T>(storage_); }
// Unsafe access to the underlying error. Use only if you know |this| is in // Unsafe access to the underlying error. Use only if you know |this| is in
// the requested state. // the requested state.
E Error() { return absl::get<E>(storage_); } E& Error() { return absl::get<E>(storage_); }
// Safe access to the underlying value, or nullptr; // Safe access to the underlying value, or nullptr;
T* MaybeValue() { return absl::get_if<T>(&storage_); } T* MaybeValue() { return absl::get_if<T>(&storage_); }
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_BOREALIS_INFRA_STATE_MANAGER_H_
#define CHROME_BROWSER_CHROMEOS_BOREALIS_INFRA_STATE_MANAGER_H_
#include <memory>
#include "base/callback_list.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "chrome/browser/chromeos/borealis/infra/expected.h"
#include "chrome/browser/chromeos/borealis/infra/transition.h"
namespace borealis {
// A tool for organizing callbacks that depend on the system being in a certain
// state, represented by an object of type |State|. Clients of the state manager
// can make requests that the system transition between the "On" and "Off"
// states, which relate to the presence or absence of the |State| instance.
//
// A request to transition the system comes in the form of a callback, which
// will be invoked asynchronously when the system is in the requested state. The
// system will transition based on the most recent callback.
//
// The state manager can itself be in one of 4 phases:
//
// [Off] <-----> {Transitioning On}
// ^ |
// | v
// {Transitioning Off} <------ [On]
//
// These phases relate to |State| instances as well as the enqueueing of
// callbacks:
// - |State| is nonexistent while "Off", is immediately created at the start of
// "Transitioning On", exists while "On", and is immediately deleted at the
// start of "Transitioning Off"
// - When the system is "Off", a callback to turn the system "On" will be
// enqueued, and move the manger to "Transitioning On". Additional requests
// will be enqueued until the system is "On", when all queued callbacks will
// be invoked (as will new requests). Vice-versa is true for turning the
// system "Off" when in the "On" state.
// - When a system is "Transitioning On", all requests to turn the system "Off"
// will be rejected, until the system is "On", then they will be enqueued.
// Vice-versa is true for turning the system "On" in the "Transitioning Off"
// state.
//
// A system can only fail to turn "On", in which case the |State| instance will
// be deleted and the failure |OnError| will be reported to callbacks. A system
// can always transition to "Off" but it may do so uncleanly, which will result
// in an |OffError| being reported.
template <typename State, typename OnError, typename OffError>
class BorealisStateManager {
public:
// An empty state which is a marker that the state manager is off. This state
// holds no data,
struct OffState {};
// Convenience typedefs.
using OnTransition = Transition<OffState, State, OnError>;
using OffTransition = Transition<State, OffState, OffError>;
using WhenOn = void(Expected<State*, OnError>);
using WhenOff = void(base::Optional<OffError>);
// Create the state object, turning the state to "on". The |callback| will be
// invoked on completion with the result.
void TurnOn(base::OnceCallback<WhenOn> callback) {
switch (GetPhase()) {
case Phase::kOff:
on_transition_ = GetOnTransition();
on_transition_->Begin(std::make_unique<OffState>(),
base::BindOnce(&BorealisStateManager::CompleteOn,
weak_ptr_factory_.GetWeakPtr()));
pending_on_callbacks_.AddUnsafe(std::move(callback));
break;
case Phase::kTransitioningOn:
pending_on_callbacks_.AddUnsafe(std::move(callback));
break;
case Phase::kOn:
std::move(callback).Run(Expected<State*, OnError>(instance_.get()));
break;
case Phase::kTransitioningOff:
std::move(callback).Run(Unexpected<State*>(GetIsTurningOffError()));
break;
}
}
// Remove the state object, turning the state to "off". The |callback| will be
// invoked on completion with an error (or not, if successful).
void TurnOff(base::OnceCallback<WhenOff> callback) {
switch (GetPhase()) {
case Phase::kOff:
std::move(callback).Run(base::nullopt);
break;
case Phase::kTransitioningOn:
std::move(callback).Run(GetIsTurningOnError());
break;
case Phase::kOn:
off_transition_ = GetOffTransition();
off_transition_->Begin(
std::move(instance_),
base::BindOnce(&BorealisStateManager::CompleteOff,
weak_ptr_factory_.GetWeakPtr()));
pending_off_callbacks_.AddUnsafe(std::move(callback));
break;
case Phase::kTransitioningOff:
pending_off_callbacks_.AddUnsafe(std::move(callback));
break;
}
}
protected:
// Factory method to build the transition needed to turn this state manager
// on.
virtual std::unique_ptr<OnTransition> GetOnTransition() = 0;
// Factory method to build the transition needed to turn this state manager
// off.
virtual std::unique_ptr<OffTransition> GetOffTransition() = 0;
// Factory method to build the error which was the result of rejecting a
// request to turn the state on.
virtual OnError GetIsTurningOffError() = 0;
// Factory method to build the error which was the result of rejecting a
// request to turn the state off.
virtual OffError GetIsTurningOnError() = 0;
private:
// The phases of managing a state object.
enum class Phase {
kOff,
kTransitioningOn,
kOn,
kTransitioningOff,
};
// Returns the current phase of the state manager.
Phase GetPhase() const {
if (instance_) {
return Phase::kOn;
} else if (on_transition_) {
DCHECK(!off_transition_);
return Phase::kTransitioningOn;
} else if (off_transition_) {
return Phase::kTransitioningOff;
} else {
return Phase::kOff;
}
}
void CompleteOn(typename OnTransition::Result on_result) {
if (on_result) {
std::swap(instance_, on_result.Value());
on_transition_.reset();
pending_on_callbacks_.Notify(Expected<State*, OnError>(instance_.get()));
} else {
on_transition_.reset();
pending_on_callbacks_.Notify(Unexpected<State*>(on_result.Error()));
}
}
void CompleteOff(typename OffTransition::Result off_result) {
off_transition_.reset();
pending_off_callbacks_.Notify(
off_result ? base::nullopt
: base::Optional<OffError>(off_result.Error()));
}
std::unique_ptr<State> instance_;
std::unique_ptr<OnTransition> on_transition_;
std::unique_ptr<OffTransition> off_transition_;
base::OnceCallbackList<WhenOn> pending_on_callbacks_;
base::OnceCallbackList<WhenOff> pending_off_callbacks_;
base::WeakPtrFactory<BorealisStateManager<State, OnError, OffError>>
weak_ptr_factory_{this};
};
} // namespace borealis
#endif // CHROME_BROWSER_CHROMEOS_BOREALIS_INFRA_STATE_MANAGER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/borealis/infra/state_manager.h"
#include <memory>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/optional.h"
#include "base/test/task_environment.h"
#include "chrome/browser/chromeos/borealis/infra/expected.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace borealis {
namespace {
template <typename F>
class CallbackFactory : public testing::StrictMock<testing::MockFunction<F>> {
public:
base::OnceCallback<F> GetOnce() {
return base::BindOnce(&CallbackFactory<F>::Call, base::Unretained(this));
}
};
struct Foo {
std::string msg;
};
struct Bar {
std::string msg;
};
struct Baz {
std::string msg;
};
class MockStateManager : public BorealisStateManager<Foo, Bar, Baz> {
public:
MOCK_METHOD(std::unique_ptr<OnTransition>, GetOnTransition, (), ());
MOCK_METHOD(std::unique_ptr<OffTransition>, GetOffTransition, (), ());
MOCK_METHOD(Bar, GetIsTurningOffError, (), ());
MOCK_METHOD(Baz, GetIsTurningOnError, (), ());
};
class NonCompletingOnTransition : public MockStateManager::OnTransition {
void Start(std::unique_ptr<MockStateManager::OffState> in) override {}
};
class SucceedingOnTransition : public MockStateManager::OnTransition {
void Start(std::unique_ptr<MockStateManager::OffState> in) override {
Succeed(std::make_unique<Foo>());
}
};
class FailingOnTransition : public MockStateManager::OnTransition {
void Start(std::unique_ptr<MockStateManager::OffState> in) override {
Fail(Bar{.msg = "failure turning on"});
}
};
class NonCompletingOffTransition : public MockStateManager::OffTransition {
void Start(std::unique_ptr<Foo> in) override {}
};
class SucceedingOffTransition : public MockStateManager::OffTransition {
void Start(std::unique_ptr<Foo> in) override { Succeed(nullptr); }
};
class FailingOffTransition : public MockStateManager::OffTransition {
void Start(std::unique_ptr<Foo> in) override {
Fail(Baz{.msg = "failure turning off"});
}
};
TEST(BorealisStateManagerTest, DefaultStateIsOff) {
testing::StrictMock<MockStateManager> state_manager;
CallbackFactory<MockStateManager::WhenOff> on_callback_handler;
// State managers are created in the "Off" state, so we don't need to
// transition there.
EXPECT_CALL(state_manager, GetOffTransition).Times(0);
EXPECT_CALL(on_callback_handler, Call(testing::Eq(base::nullopt)));
state_manager.TurnOff(on_callback_handler.GetOnce());
}
TEST(BorealisStateManagerTest, CanBeTurnedOnAndOff) {
base::test::SingleThreadTaskEnvironment task_environment;
testing::StrictMock<MockStateManager> state_manager;
CallbackFactory<MockStateManager::WhenOn> on_callback_handler;
CallbackFactory<MockStateManager::WhenOff> off_callback_handler;
EXPECT_CALL(state_manager, GetOnTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<SucceedingOnTransition>();
}));
EXPECT_CALL(state_manager, GetOffTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<SucceedingOffTransition>();
}));
EXPECT_CALL(on_callback_handler, Call(testing::_))
.WillOnce(testing::Invoke(
[](Expected<Foo*, Bar> result) { EXPECT_TRUE(result); }));
EXPECT_CALL(off_callback_handler, Call(testing::Eq(base::nullopt)));
state_manager.TurnOn(on_callback_handler.GetOnce());
task_environment.RunUntilIdle();
state_manager.TurnOff(off_callback_handler.GetOnce());
task_environment.RunUntilIdle();
}
TEST(BorealisStateManagerTest, CanHandleMultipleCallbacks) {
base::test::SingleThreadTaskEnvironment task_environment;
testing::StrictMock<MockStateManager> state_manager;
CallbackFactory<MockStateManager::WhenOn> on_callback_handler;
EXPECT_CALL(state_manager, GetOnTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<SucceedingOnTransition>();
}));
state_manager.TurnOn(on_callback_handler.GetOnce());
state_manager.TurnOn(on_callback_handler.GetOnce());
// The above two callbacks will not be run until the sequence gets a chance to
// execute. We assure this by making the expectations after them.
EXPECT_CALL(on_callback_handler, Call(testing::_))
.Times(3)
.WillRepeatedly(testing::Invoke(
[](Expected<Foo*, Bar> result) { EXPECT_TRUE(result); }));
// The two callbacks will have a chance to run now.
task_environment.RunUntilIdle();
state_manager.TurnOn(on_callback_handler.GetOnce());
}
TEST(BorealisStateManagerTest, TurnOffRejectedWhileTurningOn) {
testing::StrictMock<MockStateManager> state_manager;
CallbackFactory<MockStateManager::WhenOff> off_callback_handler;
EXPECT_CALL(state_manager, GetOnTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<NonCompletingOnTransition>();
}));
EXPECT_CALL(state_manager, GetIsTurningOnError)
.WillOnce(testing::Return(Baz{.msg = "rejected"}));
EXPECT_CALL(off_callback_handler, Call(testing::_))
.WillOnce(testing::Invoke([](base::Optional<Baz> err) {
ASSERT_TRUE(err.has_value());
EXPECT_EQ(err->msg, "rejected");
}));
state_manager.TurnOn(base::DoNothing());
state_manager.TurnOff(off_callback_handler.GetOnce());
}
TEST(BorealisStateManagerTest, TurnOnRejectedWhileTurningOff) {
base::test::SingleThreadTaskEnvironment task_environment;
testing::StrictMock<MockStateManager> state_manager;
CallbackFactory<MockStateManager::WhenOn> on_callback_handler;
EXPECT_CALL(state_manager, GetOnTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<SucceedingOnTransition>();
}));
EXPECT_CALL(state_manager, GetOffTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<NonCompletingOffTransition>();
}));
EXPECT_CALL(state_manager, GetIsTurningOffError)
.WillOnce(testing::Return(Bar{.msg = "blocked"}));
EXPECT_CALL(on_callback_handler, Call(testing::_))
.WillOnce(testing::Invoke([](Expected<Foo*, Bar> result) {
EXPECT_TRUE(result.Unexpected());
EXPECT_EQ(result.Error().msg, "blocked");
}));
state_manager.TurnOn(base::DoNothing());
task_environment.RunUntilIdle();
state_manager.TurnOff(base::DoNothing());
state_manager.TurnOn(on_callback_handler.GetOnce());
}
TEST(BorealisStateManagerTest, FailureToTurnOnProducesAnErrorAndResultsInOff) {
base::test::SingleThreadTaskEnvironment task_environment;
testing::StrictMock<MockStateManager> state_manager;
CallbackFactory<MockStateManager::WhenOn> on_callback_handler;
EXPECT_CALL(state_manager, GetOnTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<FailingOnTransition>();
}));
EXPECT_CALL(on_callback_handler, Call(testing::_))
.WillOnce(testing::Invoke(
[](Expected<Foo*, Bar> result) { EXPECT_FALSE(result); }));
state_manager.TurnOn(on_callback_handler.GetOnce());
task_environment.RunUntilIdle();
// Additional call to turn off requires no transition, because the state is
// off.
CallbackFactory<MockStateManager::WhenOff> off_callback_handler;
EXPECT_CALL(off_callback_handler, Call(testing::Eq(base::nullopt)));
state_manager.TurnOff(off_callback_handler.GetOnce());
}
TEST(BorealisStateManagerTest, FailureToTurnOffProducesErrorButDoesTurnOff) {
base::test::SingleThreadTaskEnvironment task_environment;
testing::StrictMock<MockStateManager> state_manager;
CallbackFactory<MockStateManager::WhenOn> on_callback_handler;
CallbackFactory<MockStateManager::WhenOff> off_callback_handler;
EXPECT_CALL(state_manager, GetOnTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<SucceedingOnTransition>();
}));
EXPECT_CALL(state_manager, GetOffTransition).WillOnce(testing::Invoke([]() {
return std::make_unique<FailingOffTransition>();
}));
EXPECT_CALL(on_callback_handler, Call(testing::_))
.WillOnce(testing::Invoke(
[](Expected<Foo*, Bar> result) { EXPECT_TRUE(result); }));
EXPECT_CALL(off_callback_handler,
Call(testing::Not(testing::Eq(base::nullopt))));
state_manager.TurnOn(on_callback_handler.GetOnce());
task_environment.RunUntilIdle();
state_manager.TurnOff(off_callback_handler.GetOnce());
task_environment.RunUntilIdle();
// Additional call to turn off requires no transition, because the state is
// off.
EXPECT_CALL(off_callback_handler, Call(testing::Eq(base::nullopt)));
state_manager.TurnOff(off_callback_handler.GetOnce());
}
} // namespace
} // namespace borealis
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_BOREALIS_INFRA_TRANSITION_H_
#define CHROME_BROWSER_CHROMEOS_BOREALIS_INFRA_TRANSITION_H_
#include <memory>
#include "base/bind.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chrome/browser/chromeos/borealis/infra/expected.h"
// TODO(b/172501195): Make these available outside namespace borealis.
namespace borealis {
// A transition object is used to represent a transformation of a start state
// |S| into a terminating state |T|. The transition can fail, in which case the
// result is an error state |E|.
//
// The transition centers around a state object of type |S|, which will become
// an instance of |T| if the transition succeeds, and an error |E| if it does
// not.
//
// The transition itself is in one of three modes:
// - A "pending" transition has been created and is waiting for control of the
// |S| instance to begin the transition.
// - A "working" transition has control of the |S| object, and is transforming
// it into |T|. There is no guarantee at this point about the existence of an
// |S| or |T| instance.
// - A "final" transition has completed, and either succeeded (in which case
// a |T| instance exists) or failed (in which case |T| does not exist and an
// |E| error has been generated.
//
// Once the transition is "final" no further work is possible and the transition
// can be deleted.
template <typename S, typename T, typename E>
class Transition {
public:
using Result = Expected<std::unique_ptr<T>, E>;
using OnCompleteSignature = void(Result);
using OnCompleteCallback = base::OnceCallback<OnCompleteSignature>;
Transition() = default;
virtual ~Transition() = default;
// Begin the transition, marking this transition as "working" and giving
// ownership of the |S| object to the transition.
void Begin(std::unique_ptr<S> start_instance, OnCompleteCallback callback) {
callback_ = std::move(callback);
Start(std::move(start_instance));
}
protected:
// Override this method to define the work that this transition performs. As
// soon as this function is entered, the transition is "working", until you
// call Succeed() or Fail(), which should be the last things you call.
virtual void Start(std::unique_ptr<S> start_instance) = 0;
// Called when the transition has completed successfully. This should be the
// last thing you do.
void Succeed(std::unique_ptr<T> terminating_instance) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_),
Result(std::move(terminating_instance))));
}
// Called when the transition has completed unsuccessfully. This should only
// be called at the very end of the failing transition (including cleanup if
// needed).
void Fail(E error) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_),
Result::Unexpected(std::move(error))));
}
private:
OnCompleteCallback callback_;
};
} // namespace borealis
#endif // CHROME_BROWSER_CHROMEOS_BOREALIS_INFRA_TRANSITION_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/borealis/infra/transition.h"
#include <memory>
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace borealis {
namespace {
template <typename F>
class CallbackFactory : public testing::StrictMock<testing::MockFunction<F>> {
public:
base::OnceCallback<F> GetOnce() {
return base::BindOnce(&CallbackFactory<F>::Call, base::Unretained(this));
}
};
class ParseIntTransition : public Transition<std::string, int, bool> {
void Start(std::unique_ptr<std::string> in) override {
int val;
if (base::StringToInt(*in, &val)) {
Succeed(std::make_unique<int>(val));
} else {
Fail(false);
}
}
};
TEST(TransitionTest, TransitionCanTransformInputToOutput) {
base::test::SingleThreadTaskEnvironment task_environment;
ParseIntTransition transition;
CallbackFactory<ParseIntTransition::OnCompleteSignature> callback_handler;
EXPECT_CALL(callback_handler, Call(testing::_))
.WillOnce(testing::Invoke([](ParseIntTransition::Result result) {
ASSERT_TRUE(result);
EXPECT_EQ(*result.Value(), 12345);
}));
transition.Begin(std::make_unique<std::string>("12345"),
callback_handler.GetOnce());
task_environment.RunUntilIdle();
}
TEST(TransitionTest, TransitionCanFail) {
base::test::SingleThreadTaskEnvironment task_environment;
ParseIntTransition transition;
CallbackFactory<ParseIntTransition::OnCompleteSignature> callback_handler;
EXPECT_CALL(callback_handler, Call(testing::_))
.WillOnce(testing::Invoke([](ParseIntTransition::Result result) {
EXPECT_TRUE(result.Unexpected());
}));
transition.Begin(std::make_unique<std::string>("not a number"),
callback_handler.GetOnce());
task_environment.RunUntilIdle();
}
} // namespace
} // namespace borealis
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