blob: a557f24b5ab3dc9a110eb59a35b5918a3eba70bf [file] [log] [blame]
// Copyright 2023 The Fuchsia 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 "src/devices/power/drivers/fusb302/state-machine-base.h"
#include <lib/driver/logging/cpp/logger.h>
#include <lib/inspect/cpp/vmo/types.h>
#include <lib/inspect/testing/cpp/zxtest/inspect.h>
#include <zircon/assert.h>
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <utility>
#include <fbl/vector.h>
#include <zxtest/zxtest.h>
namespace fusb302 {
namespace {
enum class MockState : int32_t {
kInitial = 1,
kWaitingForInput = 2,
kLoopDone = 3,
};
class MockInput {
public:
MockInput() = default;
// Not copyable, to ensure that StateMachineBase doesn't try to copy inputs.
MockInput(const MockInput&) = delete;
MockInput& operator=(const MockInput&) = delete;
~MockInput() = default;
};
class MockStateMachine : public StateMachineBase<MockStateMachine, MockState, const MockInput&> {
public:
explicit MockStateMachine(inspect::Node root_node)
: StateMachineBase(MockState::kInitial, std::move(root_node), "MockStateMachine") {}
MockStateMachine(const MockStateMachine&) = delete;
MockStateMachine& operator=(const MockStateMachine&) = delete;
~MockStateMachine() override = default;
const char* StateToString(MockState state) const override {
switch (state) {
case MockState::kInitial:
return "Initial";
case MockState::kWaitingForInput:
return "WaitingForInput";
case MockState::kLoopDone:
return "LoopDone";
}
ZX_DEBUG_ASSERT_MSG(false, "Invalid MockState: %" PRId32, static_cast<int32_t>(state));
return "(invalid)";
}
void ExpectEnterState(MockState state) {
expected_calls_.push_back({.call_type = CallType::kEnterState, .state = state});
}
void ExpectEvaluateInput(const MockInput& input, MockState current_state, MockState next_state) {
expected_calls_.push_back({
.call_type = CallType::kEvaluateInput,
.state = current_state,
.mock_input = &input,
.next_state = next_state,
});
}
void ExpectExitState(MockState state) {
expected_calls_.push_back({.call_type = CallType::kExitState, .state = state});
}
void CheckAllAccessesReplayed() { EXPECT_EQ(expected_calls_.size(), expected_call_index_); }
void ForceStateTransition(MockState new_state) {
StateMachineBase::ForceStateTransition(new_state);
}
protected:
void EnterState(MockState state) override {
CheckCall({.call_type = CallType::kEnterState, .state = state});
}
MockState NextState(const MockInput& input, MockState current_state) override {
// We'll return `current_state` if we don't have a suitable expectation.
MockState next_state = current_state;
if (expected_call_index_ < expected_calls_.size()) {
next_state = expected_calls_[expected_call_index_].next_state.value_or(current_state);
}
CheckCall({
.call_type = CallType::kEvaluateInput,
.state = current_state,
.mock_input = &input,
.next_state = std::nullopt,
});
return next_state;
}
void ExitState(MockState state) override {
CheckCall({.call_type = CallType::kExitState, .state = state});
}
private:
enum class CallType : int32_t {
kEnterState = 1,
kEvaluateInput = 2,
kExitState = 3,
};
struct CallInfo {
CallType call_type;
MockState state;
// nullptr if `call_type` is not `kEvaluateInput`.
const MockInput* mock_input = nullptr;
// nullopt if `call_type` is not `kEvaluateInput` or for actual calls.
std::optional<MockState> next_state = std::nullopt;
};
void CheckCall(const CallInfo& actual_call) {
ASSERT_LT(expected_call_index_, expected_calls_.size());
const CallInfo& expected_call = expected_calls_[expected_call_index_];
++expected_call_index_;
EXPECT_EQ(expected_call.call_type, actual_call.call_type);
EXPECT_EQ(expected_call.state, actual_call.state);
EXPECT_EQ(expected_call.mock_input, actual_call.mock_input);
}
fbl::Vector<CallInfo> expected_calls_;
mutable size_t expected_call_index_ = 0;
};
class StateMachineBaseTest : public inspect::InspectTestHelper, public zxtest::Test {
public:
void SetUp() override { fdf::Logger::SetGlobalInstance(&logger_); }
void TearDown() override {
state_machine_.CheckAllAccessesReplayed();
fdf::Logger::SetGlobalInstance(nullptr);
}
void ExpectInspectStateEquals(MockState state) {
ASSERT_NO_FATAL_FAILURE(ReadInspect(inspect_.DuplicateVmo()));
auto* state_machine_root = hierarchy().GetByPath({"MockStateMachine"});
ASSERT_TRUE(state_machine_root);
CheckProperty(state_machine_root->node(), "State",
inspect::IntPropertyValue(static_cast<int32_t>(state)));
}
protected:
fdf::Logger logger_{"state-machine-base-test", FUCHSIA_LOG_DEBUG, zx::socket{},
fidl::WireClient<fuchsia_logger::LogSink>()};
inspect::Inspector inspect_;
MockStateMachine state_machine_{inspect_.GetRoot().CreateChild("MockStateMachine")};
};
TEST_F(StateMachineBaseTest, ConstructorSetsInitialState) {
EXPECT_EQ(MockState::kInitial, state_machine_.current_state());
ExpectInspectStateEquals(MockState::kInitial);
}
TEST_F(StateMachineBaseTest, EvaluateInputEntry) {
MockInput input1;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input1, MockState::kInitial, MockState::kInitial);
state_machine_.Run(input1);
EXPECT_EQ(MockState::kInitial, state_machine_.current_state());
}
TEST_F(StateMachineBaseTest, EvaluateInputNoChange) {
MockInput input1, input2;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input1, MockState::kInitial, MockState::kInitial);
state_machine_.ExpectEvaluateInput(input2, MockState::kInitial, MockState::kInitial);
state_machine_.Run(input1);
state_machine_.Run(input2);
EXPECT_EQ(MockState::kInitial, state_machine_.current_state());
}
TEST_F(StateMachineBaseTest, EvaluateInputEntryTransition) {
MockInput input1;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input1, MockState::kInitial, MockState::kWaitingForInput);
state_machine_.ExpectExitState(MockState::kInitial);
state_machine_.ExpectEnterState(MockState::kWaitingForInput);
state_machine_.ExpectEvaluateInput(input1, MockState::kWaitingForInput,
MockState::kWaitingForInput);
state_machine_.Run(input1);
EXPECT_EQ(MockState::kWaitingForInput, state_machine_.current_state());
ExpectInspectStateEquals(MockState::kWaitingForInput);
}
TEST_F(StateMachineBaseTest, EvaluateInputLoop) {
MockInput input1, input2, input3, input4;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input1, MockState::kInitial, MockState::kWaitingForInput);
state_machine_.ExpectExitState(MockState::kInitial);
state_machine_.ExpectEnterState(MockState::kWaitingForInput);
state_machine_.ExpectEvaluateInput(input1, MockState::kWaitingForInput,
MockState::kWaitingForInput);
state_machine_.ExpectEvaluateInput(input2, MockState::kWaitingForInput,
MockState::kWaitingForInput);
state_machine_.ExpectEvaluateInput(input3, MockState::kWaitingForInput,
MockState::kWaitingForInput);
state_machine_.ExpectEvaluateInput(input4, MockState::kWaitingForInput, MockState::kLoopDone);
state_machine_.ExpectExitState(MockState::kWaitingForInput);
state_machine_.ExpectEnterState(MockState::kLoopDone);
state_machine_.ExpectEvaluateInput(input4, MockState::kLoopDone, MockState::kLoopDone);
state_machine_.Run(input1);
state_machine_.Run(input2);
EXPECT_EQ(MockState::kWaitingForInput, state_machine_.current_state());
state_machine_.Run(input3);
EXPECT_EQ(MockState::kWaitingForInput, state_machine_.current_state());
state_machine_.Run(input4);
EXPECT_EQ(MockState::kLoopDone, state_machine_.current_state());
ExpectInspectStateEquals(MockState::kLoopDone);
}
TEST_F(StateMachineBaseTest, ForceStateTransitionNoTransitionBeforeEntry) {
MockInput input;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input, MockState::kInitial, MockState::kInitial);
state_machine_.ForceStateTransition(MockState::kInitial);
EXPECT_EQ(MockState::kInitial, state_machine_.current_state());
state_machine_.Run(input);
EXPECT_EQ(MockState::kInitial, state_machine_.current_state());
}
TEST_F(StateMachineBaseTest, ForceStateTransitionNoTransitionAfterEntry) {
MockInput input1, input2;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input1, MockState::kInitial, MockState::kInitial);
state_machine_.ExpectEvaluateInput(input2, MockState::kInitial, MockState::kInitial);
state_machine_.Run(input1);
state_machine_.ForceStateTransition(MockState::kInitial);
EXPECT_EQ(MockState::kInitial, state_machine_.current_state());
state_machine_.Run(input2);
}
TEST_F(StateMachineBaseTest, ForceStateTransitionStateChangeCausesExit) {
MockInput input;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input, MockState::kInitial, MockState::kInitial);
state_machine_.ExpectExitState(MockState::kInitial);
state_machine_.Run(input);
state_machine_.ForceStateTransition(MockState::kLoopDone);
EXPECT_EQ(MockState::kLoopDone, state_machine_.current_state());
}
TEST_F(StateMachineBaseTest, ForceStateTransitionStateChangeAfterConstructorNoExit) {
// No transitions occur.
state_machine_.ForceStateTransition(MockState::kLoopDone);
EXPECT_EQ(MockState::kLoopDone, state_machine_.current_state());
}
TEST_F(StateMachineBaseTest, ForceStateTransitionStateChangeCausesDeferedEnter) {
MockInput input1, input2;
state_machine_.ExpectEnterState(MockState::kInitial);
state_machine_.ExpectEvaluateInput(input1, MockState::kInitial, MockState::kInitial);
state_machine_.ExpectExitState(MockState::kInitial);
state_machine_.ExpectEnterState(MockState::kLoopDone);
state_machine_.ExpectEvaluateInput(input2, MockState::kLoopDone, MockState::kLoopDone);
state_machine_.Run(input1);
state_machine_.ForceStateTransition(MockState::kLoopDone);
EXPECT_EQ(MockState::kLoopDone, state_machine_.current_state());
state_machine_.Run(input2);
}
TEST_F(StateMachineBaseTest, ForceStateTransitionStateChangeAfterConstructorNoExitDeferedEnter) {
MockInput input;
state_machine_.ExpectEnterState(MockState::kLoopDone);
state_machine_.ExpectEvaluateInput(input, MockState::kLoopDone, MockState::kLoopDone);
state_machine_.ForceStateTransition(MockState::kLoopDone);
EXPECT_EQ(MockState::kLoopDone, state_machine_.current_state());
state_machine_.Run(input);
}
} // namespace
} // namespace fusb302