Merge changes Ic8eaee53,Ide961d91,Ie97f3ce9

* changes:
  audio: Allow going to 'IDLE' for synchronous drain
  audio: Improve test coverage
  audio VTS: Refactor support for non-deterministic SM behavior
This commit is contained in:
Treehugger Robot 2023-01-09 08:51:53 +00:00 committed by Gerrit Code Review
commit 99d4458c3b
7 changed files with 513 additions and 202 deletions

View file

@ -30,6 +30,7 @@ digraph stream_out_async_state_machine {
STANDBY -> PAUSED [label="burst"]; // producer -> active
IDLE -> STANDBY [label="standby"]; // consumer -> passive
IDLE -> TRANSFERRING [label="burst"]; // producer -> active
IDLE -> ACTIVE [label="burst"]; // full write
ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming)
ACTIVE -> DRAINING [label="drain"]; // producer -> passive
ACTIVE -> TRANSFERRING [label="burst"]; // early unblocking
@ -45,6 +46,7 @@ digraph stream_out_async_state_machine {
PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared
DRAINING -> IDLE [label="←IStreamCallback.onDrainReady"];
DRAINING -> TRANSFERRING [label="burst"]; // producer -> active
DRAINING -> ACTIVE [label="burst"]; // full write
DRAINING -> DRAIN_PAUSED [label="pause"]; // consumer -> passive (not consuming)
DRAIN_PAUSED -> DRAINING [label="start"]; // consumer -> active
DRAIN_PAUSED -> TRANSFER_PAUSED [label="burst"]; // producer -> active

View file

@ -31,6 +31,7 @@ digraph stream_out_state_machine {
ACTIVE -> ACTIVE [label="burst"];
ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming)
ACTIVE -> DRAINING [label="drain"]; // producer -> passive
ACTIVE -> IDLE [label="drain"]; // synchronous drain
PAUSED -> PAUSED [label="burst"];
PAUSED -> ACTIVE [label="start"]; // consumer -> active
PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared

View file

@ -46,6 +46,7 @@ using aidl::android::media::audio::common::AudioPort;
using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioProfile;
using aidl::android::media::audio::common::Boolean;
using aidl::android::media::audio::common::Int;
using aidl::android::media::audio::common::PcmType;
using android::hardware::audio::common::getFrameSizeInBytes;
@ -138,12 +139,15 @@ ndk::ScopedAStatus Module::createStreamContext(int32_t in_portConfigId, int64_t
(flags.getTag() == AudioIoFlags::Tag::output &&
!isBitPositionFlagSet(flags.get<AudioIoFlags::Tag::output>(),
AudioOutputFlags::MMAP_NOIRQ))) {
StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
mVendorDebug.forceTransientBurst,
mVendorDebug.forceSynchronousDrain};
StreamContext temp(
std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
portConfigIt->format.value(), portConfigIt->channelMask.value(),
std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
asyncCallback, mDebug.streamTransientStateDelayMs);
asyncCallback, params);
if (temp.isValid()) {
*out_context = std::move(temp);
} else {
@ -976,18 +980,69 @@ ndk::ScopedAStatus Module::generateHwAvSyncId(int32_t* _aidl_return) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
std::vector<VendorParameter>* _aidl_return) {
LOG(DEBUG) << __func__ << ": id count: " << in_ids.size();
(void)_aidl_return;
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
bool allParametersKnown = true;
for (const auto& id : in_ids) {
if (id == VendorDebug::kForceTransientBurstName) {
VendorParameter forceTransientBurst{.id = id};
forceTransientBurst.ext.setParcelable(Boolean{mVendorDebug.forceTransientBurst});
_aidl_return->push_back(std::move(forceTransientBurst));
} else if (id == VendorDebug::kForceSynchronousDrainName) {
VendorParameter forceSynchronousDrain{.id = id};
forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
_aidl_return->push_back(std::move(forceSynchronousDrain));
} else {
allParametersKnown = false;
LOG(ERROR) << __func__ << ": unrecognized parameter \"" << id << "\"";
}
}
if (allParametersKnown) return ndk::ScopedAStatus::ok();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
namespace {
template <typename W>
bool extractParameter(const VendorParameter& p, decltype(W::value)* v) {
std::optional<W> value;
binder_status_t result = p.ext.getParcelable(&value);
if (result == STATUS_OK && value.has_value()) {
*v = value.value().value;
return true;
}
LOG(ERROR) << __func__ << ": failed to read the value of the parameter \"" << p.id
<< "\": " << result;
return false;
}
} // namespace
ndk::ScopedAStatus Module::setVendorParameters(const std::vector<VendorParameter>& in_parameters,
bool in_async) {
LOG(DEBUG) << __func__ << ": parameter count " << in_parameters.size()
<< ", async: " << in_async;
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
bool allParametersKnown = true;
for (const auto& p : in_parameters) {
if (p.id == VendorDebug::kForceTransientBurstName) {
if (!extractParameter<Boolean>(p, &mVendorDebug.forceTransientBurst)) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
} else if (p.id == VendorDebug::kForceSynchronousDrainName) {
if (!extractParameter<Boolean>(p, &mVendorDebug.forceSynchronousDrain)) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
} else {
allParametersKnown = false;
LOG(ERROR) << __func__ << ": unrecognized parameter \"" << p.id << "\"";
}
}
if (allParametersKnown) return ndk::ScopedAStatus::ok();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
ndk::ScopedAStatus Module::addDeviceEffect(

View file

@ -402,7 +402,11 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
usleep(1000); // Simulate a blocking call into the driver.
populateReply(&reply, mIsConnected);
// Can switch the state to ERROR if a driver error occurs.
switchToTransientState(StreamDescriptor::State::DRAINING);
if (mState == StreamDescriptor::State::ACTIVE && mForceSynchronousDrain) {
mState = StreamDescriptor::State::IDLE;
} else {
switchToTransientState(StreamDescriptor::State::DRAINING);
}
} else if (mState == StreamDescriptor::State::TRANSFER_PAUSED) {
mState = StreamDescriptor::State::DRAIN_PAUSED;
populateReply(&reply, mIsConnected);
@ -467,14 +471,19 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
bool StreamOutWorkerLogic::write(size_t clientSize, StreamDescriptor::Reply* reply) {
const size_t readByteCount = mDataMQ->availableToRead();
// Amount of data that the HAL module is going to actually use.
const size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
bool fatal = false;
if (bool success = readByteCount > 0 ? mDataMQ->read(&mDataBuffer[0], readByteCount) : true) {
const bool isConnected = mIsConnected;
LOG(DEBUG) << __func__ << ": reading of " << readByteCount << " bytes from data MQ"
<< " succeeded; connected? " << isConnected;
// Frames are consumed and counted regardless of connection status.
// Amount of data that the HAL module is going to actually use.
size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
if (byteCount >= mFrameSize && mForceTransientBurst) {
// In order to prevent the state machine from going to ACTIVE state,
// simulate partial write.
byteCount -= mFrameSize;
}
// Frames are consumed and counted regardless of the connection status.
reply->fmqByteCount += byteCount;
mFrameCount += byteCount / mFrameSize;
populateReply(reply, isConnected);

View file

@ -36,6 +36,13 @@ class Module : public BnModule {
explicit Module(Type type) : mType(type) {}
private:
struct VendorDebug {
static const std::string kForceTransientBurstName;
static const std::string kForceSynchronousDrainName;
bool forceTransientBurst = false;
bool forceSynchronousDrain = false;
};
ndk::ScopedAStatus setModuleDebug(
const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override;
ndk::ScopedAStatus getTelephony(std::shared_ptr<ITelephony>* _aidl_return) override;
@ -128,6 +135,7 @@ class Module : public BnModule {
const Type mType;
std::unique_ptr<internal::Configuration> mConfig;
ModuleDebug mDebug;
VendorDebug mVendorDebug;
// For the interfaces requiring to return the same instance, we need to hold them
// via a strong pointer. The binder token is retained for a call to 'setMinSchedulerPolicy'.
std::shared_ptr<ITelephony> mTelephony;

View file

@ -62,12 +62,21 @@ class StreamContext {
// Ensure that this value is not used by any of StreamDescriptor.State enums
static constexpr int32_t STATE_CLOSED = -1;
struct DebugParameters {
// An extra delay for transient states, in ms.
int transientStateDelayMs = 0;
// Force the "burst" command to move the SM to the TRANSFERRING state.
bool forceTransientBurst = false;
// Force the "drain" command to be synchronous, going directly to the IDLE state.
bool forceSynchronousDrain = false;
};
StreamContext() = default;
StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
const ::aidl::android::media::audio::common::AudioFormatDescription& format,
const ::aidl::android::media::audio::common::AudioChannelLayout& channelLayout,
std::unique_ptr<DataMQ> dataMQ, std::shared_ptr<IStreamCallback> asyncCallback,
int transientStateDelayMs)
DebugParameters debugParameters)
: mCommandMQ(std::move(commandMQ)),
mInternalCommandCookie(std::rand()),
mReplyMQ(std::move(replyMQ)),
@ -75,7 +84,7 @@ class StreamContext {
mChannelLayout(channelLayout),
mDataMQ(std::move(dataMQ)),
mAsyncCallback(asyncCallback),
mTransientStateDelayMs(transientStateDelayMs) {}
mDebugParameters(debugParameters) {}
StreamContext(StreamContext&& other)
: mCommandMQ(std::move(other.mCommandMQ)),
mInternalCommandCookie(other.mInternalCommandCookie),
@ -83,8 +92,8 @@ class StreamContext {
mFormat(other.mFormat),
mChannelLayout(other.mChannelLayout),
mDataMQ(std::move(other.mDataMQ)),
mAsyncCallback(other.mAsyncCallback),
mTransientStateDelayMs(other.mTransientStateDelayMs) {}
mAsyncCallback(std::move(other.mAsyncCallback)),
mDebugParameters(std::move(other.mDebugParameters)) {}
StreamContext& operator=(StreamContext&& other) {
mCommandMQ = std::move(other.mCommandMQ);
mInternalCommandCookie = other.mInternalCommandCookie;
@ -92,8 +101,8 @@ class StreamContext {
mFormat = std::move(other.mFormat);
mChannelLayout = std::move(other.mChannelLayout);
mDataMQ = std::move(other.mDataMQ);
mAsyncCallback = other.mAsyncCallback;
mTransientStateDelayMs = other.mTransientStateDelayMs;
mAsyncCallback = std::move(other.mAsyncCallback);
mDebugParameters = std::move(other.mDebugParameters);
return *this;
}
@ -107,10 +116,12 @@ class StreamContext {
::aidl::android::media::audio::common::AudioFormatDescription getFormat() const {
return mFormat;
}
bool getForceTransientBurst() const { return mDebugParameters.forceTransientBurst; }
bool getForceSynchronousDrain() const { return mDebugParameters.forceSynchronousDrain; }
size_t getFrameSize() const;
int getInternalCommandCookie() const { return mInternalCommandCookie; }
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
int getTransientStateDelayMs() const { return mTransientStateDelayMs; }
int getTransientStateDelayMs() const { return mDebugParameters.transientStateDelayMs; }
bool isValid() const;
void reset();
@ -122,7 +133,7 @@ class StreamContext {
::aidl::android::media::audio::common::AudioChannelLayout mChannelLayout;
std::unique_ptr<DataMQ> mDataMQ;
std::shared_ptr<IStreamCallback> mAsyncCallback;
int mTransientStateDelayMs;
DebugParameters mDebugParameters;
};
class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
@ -141,7 +152,9 @@ class StreamWorkerCommonLogic : public ::android::hardware::audio::common::Strea
mReplyMQ(context.getReplyMQ()),
mDataMQ(context.getDataMQ()),
mAsyncCallback(context.getAsyncCallback()),
mTransientStateDelayMs(context.getTransientStateDelayMs()) {}
mTransientStateDelayMs(context.getTransientStateDelayMs()),
mForceTransientBurst(context.getForceTransientBurst()),
mForceSynchronousDrain(context.getForceSynchronousDrain()) {}
std::string init() override;
void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
void populateReplyWrongState(StreamDescriptor::Reply* reply,
@ -164,6 +177,8 @@ class StreamWorkerCommonLogic : public ::android::hardware::audio::common::Strea
std::shared_ptr<IStreamCallback> mAsyncCallback;
const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
const bool mForceTransientBurst;
const bool mForceSynchronousDrain;
// We use an array and the "size" field instead of a vector to be able to detect
// memory allocation issues.
std::unique_ptr<int8_t[]> mDataBuffer;

View file

@ -18,6 +18,7 @@
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <forward_list>
#include <limits>
#include <memory>
#include <mutex>
@ -85,6 +86,7 @@ using aidl::android::media::audio::common::AudioPortDeviceExt;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioSource;
using aidl::android::media::audio::common::AudioUsage;
using aidl::android::media::audio::common::Boolean;
using aidl::android::media::audio::common::Float;
using aidl::android::media::audio::common::Int;
using aidl::android::media::audio::common::Void;
@ -126,7 +128,7 @@ class WithDebugFlags {
return WithDebugFlags(parent.mFlags);
}
WithDebugFlags() {}
WithDebugFlags() = default;
explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {}
WithDebugFlags(const WithDebugFlags&) = delete;
WithDebugFlags& operator=(const WithDebugFlags&) = delete;
@ -135,7 +137,10 @@ class WithDebugFlags {
EXPECT_IS_OK(mModule->setModuleDebug(mInitial));
}
}
void SetUp(IModule* module) { ASSERT_IS_OK(module->setModuleDebug(mFlags)); }
void SetUp(IModule* module) {
ASSERT_IS_OK(module->setModuleDebug(mFlags));
mModule = module;
}
ModuleDebug& flags() { return mFlags; }
private:
@ -144,13 +149,65 @@ class WithDebugFlags {
IModule* mModule = nullptr;
};
template <typename T>
class WithModuleParameter {
public:
WithModuleParameter(const std::string parameterId, const T& value)
: mParameterId(parameterId), mValue(value) {}
WithModuleParameter(const WithModuleParameter&) = delete;
WithModuleParameter& operator=(const WithModuleParameter&) = delete;
~WithModuleParameter() {
if (mModule != nullptr) {
VendorParameter parameter{.id = mParameterId};
parameter.ext.setParcelable(mInitial);
EXPECT_IS_OK(mModule->setVendorParameters({parameter}, false));
}
}
ScopedAStatus SetUpNoChecks(IModule* module, bool failureExpected) {
std::vector<VendorParameter> parameters;
ScopedAStatus result = module->getVendorParameters({mParameterId}, &parameters);
if (result.isOk() && parameters.size() == 1) {
std::optional<T> maybeInitial;
binder_status_t status = parameters[0].ext.getParcelable(&maybeInitial);
if (status == STATUS_OK && maybeInitial.has_value()) {
mInitial = maybeInitial.value();
VendorParameter parameter{.id = mParameterId};
parameter.ext.setParcelable(mValue);
result = module->setVendorParameters({parameter}, false);
if (result.isOk()) {
LOG(INFO) << __func__ << ": overriding parameter \"" << mParameterId
<< "\" with " << mValue.toString()
<< ", old value: " << mInitial.toString();
mModule = module;
}
} else {
LOG(ERROR) << __func__ << ": error while retrieving the value of \"" << mParameterId
<< "\"";
return ScopedAStatus::fromStatus(status);
}
}
if (!result.isOk()) {
LOG(failureExpected ? INFO : ERROR)
<< __func__ << ": can not override vendor parameter \"" << mParameterId << "\""
<< result;
}
return result;
}
private:
const std::string mParameterId;
const T mValue;
IModule* mModule = nullptr;
T mInitial;
};
// For consistency, WithAudioPortConfig can start both with a non-existent
// port config, and with an existing one. Existence is determined by the
// id of the provided config. If it's not 0, then WithAudioPortConfig is
// essentially a no-op wrapper.
class WithAudioPortConfig {
public:
WithAudioPortConfig() {}
WithAudioPortConfig() = default;
explicit WithAudioPortConfig(const AudioPortConfig& config) : mInitialConfig(config) {}
WithAudioPortConfig(const WithAudioPortConfig&) = delete;
WithAudioPortConfig& operator=(const WithAudioPortConfig&) = delete;
@ -302,26 +359,31 @@ class AudioCoreModuleBase {
void SetUpImpl(const std::string& moduleName) {
ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName));
debug.flags().simulateDeviceConnections = true;
ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get()));
}
void TearDownImpl() {
if (module != nullptr) {
EXPECT_IS_OK(module->setModuleDebug(ModuleDebug{}));
}
}
void TearDownImpl() { debug.reset(); }
void ConnectToService(const std::string& moduleName) {
ASSERT_EQ(module, nullptr);
ASSERT_EQ(debug, nullptr);
module = IModule::fromBinder(binderUtil.connectToService(moduleName));
ASSERT_NE(module, nullptr);
ASSERT_NO_FATAL_FAILURE(SetUpDebug());
}
void RestartService() {
ASSERT_NE(module, nullptr);
moduleConfig.reset();
debug.reset();
module = IModule::fromBinder(binderUtil.restartService());
ASSERT_NE(module, nullptr);
ASSERT_NO_FATAL_FAILURE(SetUpDebug());
}
void SetUpDebug() {
debug.reset(new WithDebugFlags());
debug->flags().simulateDeviceConnections = true;
ASSERT_NO_FATAL_FAILURE(debug->SetUp(module.get()));
}
void ApplyEveryConfig(const std::vector<AudioPortConfig>& configs) {
@ -390,7 +452,7 @@ class AudioCoreModuleBase {
std::shared_ptr<IModule> module;
std::unique_ptr<ModuleConfig> moduleConfig;
AudioHalBinderServiceUtil binderUtil;
WithDebugFlags debug;
std::unique_ptr<WithDebugFlags> debug;
};
class AudioCoreModule : public AudioCoreModuleBase, public testing::TestWithParam<std::string> {
@ -465,6 +527,7 @@ class StreamContext {
size_t getBufferSizeFrames() const { return mBufferSizeFrames; }
CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
DataMQ* getDataMQ() const { return mDataMQ.get(); }
size_t getFrameSizeBytes() const { return mFrameSizeBytes; }
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
private:
@ -504,10 +567,48 @@ std::string toString(StreamEventReceiver::Event event) {
return std::to_string(static_cast<int32_t>(event));
}
// Note: we use a reference wrapper, not a pointer, because methods of std::*list
// return references to inserted elements. This way, we can put a returned reference
// into the children vector without any type conversions, and this makes DAG creation
// code more clear.
template <typename T>
struct DagNode : public std::pair<T, std::vector<std::reference_wrapper<DagNode<T>>>> {
using Children = std::vector<std::reference_wrapper<DagNode>>;
DagNode(const T& t, const Children& c) : std::pair<T, Children>(t, c) {}
DagNode(T&& t, Children&& c) : std::pair<T, Children>(std::move(t), std::move(c)) {}
const T& datum() const { return this->first; }
Children& children() { return this->second; }
const Children& children() const { return this->second; }
};
// Since DagNodes do contain references to next nodes, node links provided
// by the list are not used. Thus, the order of the nodes in the list is not
// important, except that the starting node must be at the front of the list,
// which means, it must always be added last.
template <typename T>
struct Dag : public std::forward_list<DagNode<T>> {
Dag() = default;
// We prohibit copying and moving Dag instances because implementing that
// is not trivial due to references between nodes.
Dag(const Dag&) = delete;
Dag(Dag&&) = delete;
Dag& operator=(const Dag&) = delete;
Dag& operator=(Dag&&) = delete;
};
// Transition to the next state happens either due to a command from the client,
// or after an event received from the server.
using TransitionTrigger = std::variant<StreamDescriptor::Command, StreamEventReceiver::Event>;
using StateTransition = std::pair<TransitionTrigger, StreamDescriptor::State>;
std::string toString(const TransitionTrigger& trigger) {
if (std::holds_alternative<StreamDescriptor::Command>(trigger)) {
return std::string("'")
.append(toString(std::get<StreamDescriptor::Command>(trigger).getTag()))
.append("' command");
}
return std::string("'")
.append(toString(std::get<StreamEventReceiver::Event>(trigger)))
.append("' event");
}
struct StateSequence {
virtual ~StateSequence() = default;
virtual void rewind() = 0;
@ -517,6 +618,10 @@ struct StateSequence {
virtual void advance(StreamDescriptor::State state) = 0;
};
// Defines the current state and the trigger to transfer to the next one,
// thus "state" is the "from" state.
using StateTransitionFrom = std::pair<StreamDescriptor::State, TransitionTrigger>;
static const StreamDescriptor::Command kGetStatusCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::getStatus>(Void{});
static const StreamDescriptor::Command kStartCommand =
@ -542,66 +647,65 @@ static const StreamEventReceiver::Event kTransferReadyEvent =
StreamEventReceiver::Event::TransferReady;
static const StreamEventReceiver::Event kDrainReadyEvent = StreamEventReceiver::Event::DrainReady;
// Handle possible bifurcations:
// - on burst and on start: 'TRANSFERRING' -> {'ACTIVE', 'TRANSFERRING'}
// - on pause: 'TRANSFER_PAUSED' -> {'PAUSED', 'TRANSFER_PAUSED'}
// It is assumed that the 'steps' provided on the construction contain the sequence
// for the async case, which gets corrected in the case when the HAL decided to do
// a synchronous transfer.
class SmartStateSequence : public StateSequence {
struct StateDag : public Dag<StateTransitionFrom> {
using Node = StateDag::reference;
using NextStates = StateDag::value_type::Children;
template <typename... Next>
Node makeNode(StreamDescriptor::State s, TransitionTrigger t, Next&&... next) {
return emplace_front(std::make_pair(s, t), NextStates{std::forward<Next>(next)...});
}
Node makeNodes(const std::vector<StateTransitionFrom>& v, Node last) {
auto helper = [&](auto i, auto&& h) -> Node {
if (i == v.end()) return last;
return makeNode(i->first, i->second, h(++i, h));
};
return helper(v.begin(), helper);
}
Node makeNodes(const std::vector<StateTransitionFrom>& v, StreamDescriptor::State f) {
return makeNodes(v, makeFinalNode(f));
}
Node makeFinalNode(StreamDescriptor::State s) {
// The actual command used here is irrelevant. Since it's the final node
// in the test sequence, no commands sent after reaching it.
return emplace_front(std::make_pair(s, kGetStatusCommand), NextStates{});
}
};
class StateSequenceFollower : public StateSequence {
public:
explicit SmartStateSequence(const std::vector<StateTransition>& steps) : mSteps(steps) {}
explicit SmartStateSequence(std::vector<StateTransition>&& steps) : mSteps(std::move(steps)) {}
void rewind() override { mCurrentStep = 0; }
bool done() const override { return mCurrentStep >= mSteps.size(); }
TransitionTrigger getTrigger() override { return mSteps[mCurrentStep].first; }
explicit StateSequenceFollower(std::unique_ptr<StateDag> steps)
: mSteps(std::move(steps)), mCurrent(mSteps->front()) {}
void rewind() override { mCurrent = mSteps->front(); }
bool done() const override { return current().children().empty(); }
TransitionTrigger getTrigger() override { return current().datum().second; }
std::set<StreamDescriptor::State> getExpectedStates() override {
std::set<StreamDescriptor::State> result = {getState()};
if (isBurstBifurcation() || isStartBifurcation()) {
result.insert(StreamDescriptor::State::ACTIVE);
} else if (isPauseBifurcation()) {
result.insert(StreamDescriptor::State::PAUSED);
}
std::set<StreamDescriptor::State> result;
std::transform(current().children().cbegin(), current().children().cend(),
std::inserter(result, result.begin()),
[](const auto& node) { return node.get().datum().first; });
LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(result);
return result;
}
void advance(StreamDescriptor::State state) override {
if (isBurstBifurcation() && state == StreamDescriptor::State::ACTIVE &&
mCurrentStep + 1 < mSteps.size() &&
mSteps[mCurrentStep + 1].first == TransitionTrigger{kTransferReadyEvent}) {
mCurrentStep++;
if (auto it = std::find_if(
current().children().cbegin(), current().children().cend(),
[&](const auto& node) { return node.get().datum().first == state; });
it != current().children().cend()) {
LOG(DEBUG) << __func__ << ": " << toString(mCurrent.get().datum().first) << " -> "
<< toString(it->get().datum().first);
mCurrent = *it;
} else {
LOG(FATAL) << __func__ << ": state " << toString(state) << " is unexpected";
}
mCurrentStep++;
}
private:
StreamDescriptor::State getState() const { return mSteps[mCurrentStep].second; }
bool isBurstBifurcation() {
return getTrigger() == TransitionTrigger{kBurstCommand} &&
getState() == StreamDescriptor::State::TRANSFERRING;
}
bool isPauseBifurcation() {
return getTrigger() == TransitionTrigger{kPauseCommand} &&
getState() == StreamDescriptor::State::TRANSFER_PAUSED;
}
bool isStartBifurcation() {
return getTrigger() == TransitionTrigger{kStartCommand} &&
getState() == StreamDescriptor::State::TRANSFERRING;
}
const std::vector<StateTransition> mSteps;
size_t mCurrentStep = 0;
StateDag::const_reference current() const { return mCurrent.get(); }
std::unique_ptr<StateDag> mSteps;
std::reference_wrapper<StateDag::value_type> mCurrent;
};
std::string toString(const TransitionTrigger& trigger) {
if (std::holds_alternative<StreamDescriptor::Command>(trigger)) {
return std::string("'")
.append(toString(std::get<StreamDescriptor::Command>(trigger).getTag()))
.append("' command");
}
return std::string("'")
.append(toString(std::get<StreamEventReceiver::Event>(trigger)))
.append("' event");
}
struct StreamLogicDriver {
virtual ~StreamLogicDriver() = default;
// Return 'true' to stop the worker.
@ -927,7 +1031,7 @@ class WithStream {
return common->close();
}
WithStream() {}
WithStream() = default;
explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {}
WithStream(const WithStream&) = delete;
WithStream& operator=(const WithStream&) = delete;
@ -1032,7 +1136,7 @@ ScopedAStatus WithStream<IStreamOut>::SetUpNoChecks(IModule* module,
class WithAudioPatch {
public:
WithAudioPatch() {}
WithAudioPatch() = default;
WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig)
: mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {}
WithAudioPatch(bool sinkIsCfg1, const AudioPortConfig& portConfig1,
@ -1473,7 +1577,7 @@ TEST_P(AudioCoreModule, TryConnectMissingDevice) {
GTEST_SKIP() << "No external devices in the module.";
}
AudioPort ignored;
WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(debug);
WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(*debug);
doNotSimulateConnections.flags().simulateDeviceConnections = false;
ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
for (const auto& port : ports) {
@ -1493,7 +1597,7 @@ TEST_P(AudioCoreModule, TryChangingConnectionSimulationMidway) {
}
WithDevicePortConnectedState portConnected(*ports.begin(), GenerateUniqueDeviceAddress());
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
ModuleDebug midwayDebugChange = debug.flags();
ModuleDebug midwayDebugChange = debug->flags();
midwayDebugChange.simulateDeviceConnections = false;
EXPECT_STATUS(EX_ILLEGAL_STATE, module->setModuleDebug(midwayDebugChange))
<< "when trying to disable connections simulation while having a connected device";
@ -2716,8 +2820,8 @@ TEST_P(AudioStreamOut, SelectPresentation) {
class StreamLogicDefaultDriver : public StreamLogicDriver {
public:
explicit StreamLogicDefaultDriver(std::shared_ptr<StateSequence> commands)
: mCommands(commands) {
StreamLogicDefaultDriver(std::shared_ptr<StateSequence> commands, size_t frameSizeBytes)
: mCommands(commands), mFrameSizeBytes(frameSizeBytes) {
mCommands->rewind();
}
@ -2736,7 +2840,10 @@ class StreamLogicDefaultDriver : public StreamLogicDriver {
if (actualSize != nullptr) {
// In the output scenario, reduce slightly the fmqByteCount to verify
// that the HAL module always consumes all data from the MQ.
if (maxDataSize > 1) maxDataSize--;
if (maxDataSize > static_cast<int>(mFrameSizeBytes)) {
LOG(DEBUG) << __func__ << ": reducing data size by " << mFrameSizeBytes;
maxDataSize -= mFrameSizeBytes;
}
*actualSize = maxDataSize;
}
command->set<StreamDescriptor::Command::Tag::burst>(maxDataSize);
@ -2782,6 +2889,7 @@ class StreamLogicDefaultDriver : public StreamLogicDriver {
protected:
std::shared_ptr<StateSequence> mCommands;
const size_t mFrameSizeBytes;
std::optional<StreamDescriptor::State> mPreviousState;
std::optional<int64_t> mPreviousFrames;
bool mObservablePositionIncrease = false;
@ -2830,7 +2938,7 @@ class AudioStreamIo : public AudioCoreModuleBase,
(!isNonBlocking && streamType == StreamTypeFilter::ASYNC)) {
continue;
}
WithDebugFlags delayTransientStates = WithDebugFlags::createNested(debug);
WithDebugFlags delayTransientStates = WithDebugFlags::createNested(*debug);
delayTransientStates.flags().streamTransientStateDelayMs =
std::get<NAMED_CMD_DELAY_MS>(std::get<PARAM_CMD_SEQ>(GetParam()));
ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get()));
@ -2841,6 +2949,40 @@ class AudioStreamIo : public AudioCoreModuleBase,
} else {
ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
}
if (isNonBlocking) {
// Also try running the same sequence with "aosp.forceTransientBurst" set.
// This will only work with the default implementation. When it works, the stream
// tries always to move to the 'TRANSFERRING' state after a burst.
// This helps to check more paths for our test scenarios.
WithModuleParameter forceTransientBurst("aosp.forceTransientBurst", Boolean{true});
if (forceTransientBurst.SetUpNoChecks(module.get(), true /*failureExpected*/)
.isOk()) {
if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
ASSERT_NO_FATAL_FAILURE(
RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
} else {
ASSERT_NO_FATAL_FAILURE(
RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
}
}
} else if (!IOTraits<Stream>::is_input) {
// Also try running the same sequence with "aosp.forceSynchronousDrain" set.
// This will only work with the default implementation. When it works, the stream
// tries always to move to the 'IDLE' state after a drain.
// This helps to check more paths for our test scenarios.
WithModuleParameter forceSynchronousDrain("aosp.forceSynchronousDrain",
Boolean{true});
if (forceSynchronousDrain.SetUpNoChecks(module.get(), true /*failureExpected*/)
.isOk()) {
if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
ASSERT_NO_FATAL_FAILURE(
RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
} else {
ASSERT_NO_FATAL_FAILURE(
RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
}
}
}
}
}
@ -2861,7 +3003,8 @@ class AudioStreamIo : public AudioCoreModuleBase,
WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
StreamLogicDefaultDriver driver(commandsAndStates);
StreamLogicDefaultDriver driver(commandsAndStates,
stream.getContext()->getFrameSizeBytes());
typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
stream.getEventReceiver());
@ -2882,7 +3025,8 @@ class AudioStreamIo : public AudioCoreModuleBase,
std::shared_ptr<StateSequence> commandsAndStates) {
WithStream<Stream> stream(portConfig);
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
StreamLogicDefaultDriver driver(commandsAndStates);
StreamLogicDefaultDriver driver(commandsAndStates,
stream.getContext()->getFrameSizeBytes());
typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
stream.getEventReceiver());
@ -3219,38 +3363,52 @@ static const int kStreamTransientStateTransitionDelayMs = 3000;
// TODO: Add async test cases for input once it is implemented.
std::shared_ptr<StateSequence> makeBurstCommands(bool isSync, size_t burstCount) {
const auto burst =
isSync ? std::vector<StateTransition>{std::make_pair(kBurstCommand,
StreamDescriptor::State::ACTIVE)}
: std::vector<StateTransition>{
std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
std::make_pair(kTransferReadyEvent, StreamDescriptor::State::ACTIVE)};
std::vector<StateTransition> result{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE)};
for (size_t i = 0; i < burstCount; ++i) {
result.insert(result.end(), burst.begin(), burst.end());
std::shared_ptr<StateSequence> makeBurstCommands(bool isSync) {
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
StateDag::Node last = d->makeFinalNode(State::ACTIVE);
StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, last);
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
// Allow optional routing via the TRANSFERRING state on bursts.
active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, last));
idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
}
return std::make_shared<SmartStateSequence>(result);
d->makeNode(State::STANDBY, kStartCommand, idle);
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kReadSeq =
std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true, 3));
static const NamedCommandSequence kWriteSyncSeq = std::make_tuple(
std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true, 3));
static const NamedCommandSequence kWriteAsyncSeq = std::make_tuple(
std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false, 3));
std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true));
static const NamedCommandSequence kWriteSyncSeq =
std::make_tuple(std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true));
static const NamedCommandSequence kWriteAsyncSeq =
std::make_tuple(std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false));
std::shared_ptr<StateSequence> makeAsyncDrainCommands(bool isInput) {
return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, isInput ? StreamDescriptor::State::ACTIVE
: StreamDescriptor::State::TRANSFERRING),
std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
StreamDescriptor::State::DRAINING),
isInput ? std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE)
: std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
StreamDescriptor::State::DRAINING)});
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
if (isInput) {
d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
std::make_pair(State::IDLE, kBurstCommand),
std::make_pair(State::ACTIVE, kDrainInCommand),
std::make_pair(State::DRAINING, kStartCommand),
std::make_pair(State::ACTIVE, kDrainInCommand)},
State::DRAINING);
} else {
StateDag::Node draining =
d->makeNodes({std::make_pair(State::DRAINING, kBurstCommand),
std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)},
State::DRAINING);
StateDag::Node idle =
d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)},
draining);
// If we get straight into ACTIVE on burst, no further testing is possible.
draining.children().push_back(d->makeFinalNode(State::ACTIVE));
idle.children().push_back(d->makeFinalNode(State::ACTIVE));
d->makeNode(State::STANDBY, kStartCommand, idle);
}
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kWriteDrainAsyncSeq =
std::make_tuple(std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs,
@ -3259,58 +3417,92 @@ static const NamedCommandSequence kDrainInSeq = std::make_tuple(
std::string("Drain"), 0, StreamTypeFilter::ANY, makeAsyncDrainCommands(true));
std::shared_ptr<StateSequence> makeDrainOutCommands(bool isSync) {
return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
std::make_pair(isSync ? TransitionTrigger(kGetStatusCommand)
: TransitionTrigger(kDrainReadyEvent),
StreamDescriptor::State::IDLE)});
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
StateDag::Node last = d->makeFinalNode(State::IDLE);
StateDag::Node active = d->makeNodes(
{std::make_pair(State::ACTIVE, kDrainOutAllCommand),
std::make_pair(State::DRAINING, isSync ? TransitionTrigger(kGetStatusCommand)
: TransitionTrigger(kDrainReadyEvent))},
last);
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
} else {
active.children().push_back(last);
}
d->makeNode(State::STANDBY, kStartCommand, idle);
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainOutSyncSeq = std::make_tuple(
std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true));
static const NamedCommandSequence kDrainOutAsyncSeq = std::make_tuple(
std::string("Drain"), 0, StreamTypeFilter::ASYNC, makeDrainOutCommands(false));
std::shared_ptr<StateSequence> makeDrainOutPauseCommands(bool isSync) {
return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
: StreamDescriptor::State::TRANSFERRING),
std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING),
std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::PAUSED
: StreamDescriptor::State::TRANSFER_PAUSED)});
std::shared_ptr<StateSequence> makeDrainPauseOutCommands(bool isSync) {
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
std::make_pair(State::DRAIN_PAUSED, kStartCommand),
std::make_pair(State::DRAINING, kPauseCommand),
std::make_pair(State::DRAIN_PAUSED, kBurstCommand)},
isSync ? State::PAUSED : State::TRANSFER_PAUSED);
StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining);
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
} else {
// If we get straight into IDLE on drain, no further testing is possible.
active.children().push_back(d->makeFinalNode(State::IDLE));
}
d->makeNode(State::STANDBY, kStartCommand, idle);
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainPauseOutSyncSeq =
std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
StreamTypeFilter::SYNC, makeDrainOutPauseCommands(true));
StreamTypeFilter::SYNC, makeDrainPauseOutCommands(true));
static const NamedCommandSequence kDrainPauseOutAsyncSeq =
std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
StreamTypeFilter::ASYNC, makeDrainOutPauseCommands(false));
StreamTypeFilter::ASYNC, makeDrainPauseOutCommands(false));
// This sequence also verifies that the capture / presentation position is not reset on standby.
std::shared_ptr<StateSequence> makeStandbyCommands(bool isInput, bool isSync) {
return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY),
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, isInput || isSync
? StreamDescriptor::State::ACTIVE
: StreamDescriptor::State::TRANSFERRING),
std::make_pair(kPauseCommand, isInput || isSync
? StreamDescriptor::State::PAUSED
: StreamDescriptor::State::TRANSFER_PAUSED),
std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
: StreamDescriptor::State::IDLE),
std::make_pair(isInput ? kGetStatusCommand : kStandbyCommand, // no-op for input
StreamDescriptor::State::STANDBY),
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, isInput || isSync
? StreamDescriptor::State::ACTIVE
: StreamDescriptor::State::TRANSFERRING)});
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
if (isInput) {
d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
std::make_pair(State::IDLE, kStandbyCommand),
std::make_pair(State::STANDBY, kStartCommand),
std::make_pair(State::IDLE, kBurstCommand),
std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kFlushCommand),
std::make_pair(State::STANDBY, kStartCommand),
std::make_pair(State::IDLE, kBurstCommand)},
State::ACTIVE);
} else {
StateDag::Node idle3 =
d->makeNode(State::IDLE, kBurstCommand, d->makeFinalNode(State::ACTIVE));
StateDag::Node idle2 = d->makeNodes({std::make_pair(State::IDLE, kStandbyCommand),
std::make_pair(State::STANDBY, kStartCommand)},
idle3);
StateDag::Node active = d->makeNodes({std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kFlushCommand)},
idle2);
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
idle3.children().push_back(d->makeFinalNode(State::TRANSFERRING));
StateDag::Node transferring =
d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)},
idle2);
idle.children().push_back(transferring);
}
d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
std::make_pair(State::IDLE, kStandbyCommand),
std::make_pair(State::STANDBY, kStartCommand)},
idle);
}
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kStandbyInSeq = std::make_tuple(
std::string("Standby"), 0, StreamTypeFilter::ANY, makeStandbyCommands(true, false));
@ -3320,50 +3512,71 @@ static const NamedCommandSequence kStandbyOutAsyncSeq =
std::make_tuple(std::string("Standby"), kStreamTransientStateTransitionDelayMs,
StreamTypeFilter::ASYNC, makeStandbyCommands(false, false));
static const NamedCommandSequence kPauseInSeq =
std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY,
std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}));
static const NamedCommandSequence kPauseOutSyncSeq =
std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC,
std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED),
std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)}));
/* TODO: Figure out a better way for testing sync/async bursts
static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple(
std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
std::make_shared<StaticStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
std::make_pair(kStartCommand, StreamDescriptor::State::TRANSFERRING),
std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAIN_PAUSED),
std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFER_PAUSED)}));
*/
std::shared_ptr<StateSequence> makePauseCommands(bool isInput, bool isSync) {
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
if (isInput) {
d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
std::make_pair(State::IDLE, kBurstCommand),
std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kBurstCommand),
std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kFlushCommand)},
State::STANDBY);
} else {
StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kStartCommand),
std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kBurstCommand),
std::make_pair(State::PAUSED, kStartCommand),
std::make_pair(State::ACTIVE, kPauseCommand)},
State::PAUSED);
if (!isSync) {
idle.children().push_back(
d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
std::make_pair(State::TRANSFER_PAUSED, kStartCommand),
std::make_pair(State::TRANSFERRING, kPauseCommand),
std::make_pair(State::TRANSFER_PAUSED, kDrainOutAllCommand),
std::make_pair(State::DRAIN_PAUSED, kBurstCommand)},
State::TRANSFER_PAUSED));
}
d->makeNode(State::STANDBY, kStartCommand, idle);
}
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kPauseInSeq = std::make_tuple(
std::string("Pause"), 0, StreamTypeFilter::ANY, makePauseCommands(true, false));
static const NamedCommandSequence kPauseOutSyncSeq = std::make_tuple(
std::string("Pause"), 0, StreamTypeFilter::SYNC, makePauseCommands(false, true));
static const NamedCommandSequence kPauseOutAsyncSeq =
std::make_tuple(std::string("Pause"), kStreamTransientStateTransitionDelayMs,
StreamTypeFilter::ASYNC, makePauseCommands(false, false));
std::shared_ptr<StateSequence> makeFlushCommands(bool isInput, bool isSync) {
return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, isInput || isSync
? StreamDescriptor::State::ACTIVE
: StreamDescriptor::State::TRANSFERRING),
std::make_pair(kPauseCommand, isInput || isSync
? StreamDescriptor::State::PAUSED
: StreamDescriptor::State::TRANSFER_PAUSED),
std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
: StreamDescriptor::State::IDLE)});
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
if (isInput) {
d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
std::make_pair(State::IDLE, kBurstCommand),
std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kFlushCommand)},
State::STANDBY);
} else {
StateDag::Node last = d->makeFinalNode(State::IDLE);
StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
std::make_pair(State::ACTIVE, kPauseCommand),
std::make_pair(State::PAUSED, kFlushCommand)},
last);
if (!isSync) {
idle.children().push_back(
d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)},
last));
}
d->makeNode(State::STANDBY, kStartCommand, idle);
}
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kFlushInSeq = std::make_tuple(
std::string("Flush"), 0, StreamTypeFilter::ANY, makeFlushCommands(true, false));
@ -3374,13 +3587,21 @@ static const NamedCommandSequence kFlushOutAsyncSeq =
StreamTypeFilter::ASYNC, makeFlushCommands(false, false));
std::shared_ptr<StateSequence> makeDrainPauseFlushOutCommands(bool isSync) {
return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
: StreamDescriptor::State::TRANSFERRING),
std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)});
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
std::make_pair(State::DRAIN_PAUSED, kFlushCommand)},
State::IDLE);
StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining);
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
} else {
// If we get straight into IDLE on drain, no further testing is possible.
active.children().push_back(d->makeFinalNode(State::IDLE));
}
d->makeNode(State::STANDBY, kStartCommand, idle);
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainPauseFlushOutSyncSeq =
std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,