blob: bd4182af279bfd79a06f2a024d06f8c953a84168 [file] [log] [blame]
// Copyright 2018 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 "peridot/bin/sessionmgr/puppet_master/dispatch_story_command_executor.h"
#include <map>
#include <memory>
#include <fuchsia/modular/cpp/fidl.h>
#include <lib/async/cpp/operation.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fidl/cpp/string.h>
#include "peridot/lib/testing/test_with_ledger.h"
#include "gtest/gtest.h"
namespace modular {
namespace {
class TestCommandRunner : public CommandRunner {
public:
using ExecuteFunc = std::function<fuchsia::modular::ExecuteStatus(
fidl::StringPtr, fuchsia::modular::StoryCommand)>;
TestCommandRunner(ExecuteFunc func, bool delay_done = false)
: func_(func), delay_done_(delay_done) {}
~TestCommandRunner() override = default;
void Execute(
fidl::StringPtr story_id, StoryStorage* const story_storage,
fuchsia::modular::StoryCommand command,
std::function<void(fuchsia::modular::ExecuteResult)> done) override {
// Post the task on the dispatcher loop to simulate a long-running task.
async::PostTask(async_get_default_dispatcher(),
[this, story_id, command = std::move(command),
done = std::move(done)]() mutable {
auto status = func_(story_id, std::move(command));
fuchsia::modular::ExecuteResult result;
result.status = status;
if (delay_done_) {
async::PostTask(async_get_default_dispatcher(),
[result = std::move(result),
done = std::move(done)]() {
done(std::move(result));
});
} else {
done(std::move(result));
}
});
}
ExecuteFunc func_;
bool delay_done_;
};
class DispatchStoryCommandExecutorTest
: public modular::testing::TestWithLedger {
protected:
void SetUp() override {
TestWithLedger::SetUp();
session_storage_ =
std::make_unique<SessionStorage>(ledger_client(), LedgerPageId());
}
void Reset() {
executor_ = std::make_unique<DispatchStoryCommandExecutor>(
session_storage_.get(), std::move(command_runners_));
}
fidl::StringPtr CreateStory() {
bool done{};
fidl::StringPtr ret;
session_storage_->CreateStory({} /* extra_info */, {} /* story_options */)
->Then([&](fidl::StringPtr story_id, fuchsia::ledger::PageId) {
ret = story_id;
done = true;
});
RunLoopUntil([&]() { return done; });
return ret;
}
void AddCommandRunner(fuchsia::modular::StoryCommand::Tag tag,
TestCommandRunner::ExecuteFunc func,
bool delay_done = false) {
command_runners_.emplace(tag, new TestCommandRunner(func, delay_done));
}
std::unique_ptr<SessionStorage> session_storage_;
std::unique_ptr<StoryCommandExecutor> executor_;
std::map<fuchsia::modular::StoryCommand::Tag, std::unique_ptr<CommandRunner>>
command_runners_;
};
TEST_F(DispatchStoryCommandExecutorTest, InvalidStory) {
Reset();
std::vector<fuchsia::modular::StoryCommand> commands;
fuchsia::modular::ExecuteResult result;
bool done{false};
executor_->ExecuteCommands("id", std::move(commands),
[&](fuchsia::modular::ExecuteResult r) {
done = true;
result = std::move(r);
});
RunLoopUntil([&]() { return done; });
EXPECT_EQ(fuchsia::modular::ExecuteStatus::INVALID_STORY_ID, result.status);
}
TEST_F(DispatchStoryCommandExecutorTest, Dispatching) {
auto expected_story_id = CreateStory();
// We expect that each command is dispatched to the command runner for that
// command.
int actual_execute_count{0};
for (auto tag : {fuchsia::modular::StoryCommand::Tag::kAddMod,
fuchsia::modular::StoryCommand::Tag::kRemoveMod,
fuchsia::modular::StoryCommand::Tag::kSetLinkValue,
fuchsia::modular::StoryCommand::Tag::kSetFocusState}) {
AddCommandRunner(tag, [tag, &actual_execute_count, expected_story_id](
fidl::StringPtr story_id,
fuchsia::modular::StoryCommand command) {
++actual_execute_count;
EXPECT_EQ(tag, command.Which());
EXPECT_EQ(expected_story_id, story_id);
return fuchsia::modular::ExecuteStatus::OK;
});
}
Reset();
std::vector<fuchsia::modular::StoryCommand> commands;
commands.resize(4);
commands[0].set_add_mod(fuchsia::modular::AddMod());
commands[1].set_remove_mod(fuchsia::modular::RemoveMod());
commands[2].set_set_link_value(fuchsia::modular::SetLinkValue());
commands[3].set_set_focus_state(fuchsia::modular::SetFocusState());
fuchsia::modular::ExecuteResult result;
bool done{false};
executor_->ExecuteCommands(expected_story_id, std::move(commands),
[&](fuchsia::modular::ExecuteResult r) {
done = true;
result = std::move(r);
});
RunLoopUntil([&]() { return done; });
EXPECT_EQ(fuchsia::modular::ExecuteStatus::OK, result.status);
EXPECT_EQ(expected_story_id, result.story_id);
EXPECT_EQ(4, actual_execute_count);
}
TEST_F(DispatchStoryCommandExecutorTest, Sequential) {
auto story_id = CreateStory();
// Commands are run sequentially.
std::vector<std::string> names;
// We're going to run an fuchsia::modular::AddMod command first, but we'll
// push the "logic" onto the async loop so that, if the implementation
// posted all of our CommandRunner logic sequentially on the async loop, it
// would run after the commands following this one. That's what |delay_done|
// does.
AddCommandRunner(
fuchsia::modular::StoryCommand::Tag::kAddMod,
[&](fidl::StringPtr story_id, fuchsia::modular::StoryCommand command) {
names.push_back(command.add_mod().mod_name.at(0));
return fuchsia::modular::ExecuteStatus::OK;
},
true /* delay_done */);
AddCommandRunner(
fuchsia::modular::StoryCommand::Tag::kRemoveMod,
[&](fidl::StringPtr story_id, fuchsia::modular::StoryCommand command) {
names.push_back(command.remove_mod().mod_name.at(0));
return fuchsia::modular::ExecuteStatus::OK;
});
Reset();
std::vector<fuchsia::modular::StoryCommand> commands;
commands.resize(2);
fuchsia::modular::AddMod add_mod;
add_mod.mod_name.push_back("one");
commands[0].set_add_mod(std::move(add_mod));
fuchsia::modular::RemoveMod remove_mod;
remove_mod.mod_name.push_back("two");
commands[1].set_remove_mod(std::move(remove_mod));
bool done{false};
executor_->ExecuteCommands(
story_id, std::move(commands),
[&](fuchsia::modular::ExecuteResult) { done = true; });
RunLoopUntil([&]() { return done; });
EXPECT_EQ(2u, names.size());
EXPECT_EQ("one", names[0]);
EXPECT_EQ("two", names[1]);
}
TEST_F(DispatchStoryCommandExecutorTest, ErrorsAbortEarly) {
auto story_id = CreateStory();
// Commands after those that report an error don't run. The reported error
// code is returned.
bool second_command_ran{false};
AddCommandRunner(
fuchsia::modular::StoryCommand::Tag::kAddMod,
[](fidl::StringPtr story_id, fuchsia::modular::StoryCommand command) {
return fuchsia::modular::ExecuteStatus::INVALID_COMMAND;
});
AddCommandRunner(
fuchsia::modular::StoryCommand::Tag::kRemoveMod,
[&](fidl::StringPtr story_id, fuchsia::modular::StoryCommand command) {
second_command_ran = true;
return fuchsia::modular::ExecuteStatus::OK;
});
Reset();
std::vector<fuchsia::modular::StoryCommand> commands;
commands.resize(2);
commands[0].set_add_mod(fuchsia::modular::AddMod());
commands[1].set_remove_mod(fuchsia::modular::RemoveMod());
bool done{false};
fuchsia::modular::ExecuteResult result;
executor_->ExecuteCommands(story_id, std::move(commands),
[&](fuchsia::modular::ExecuteResult r) {
done = true;
result = std::move(r);
});
RunLoopUntil([&]() { return done; });
EXPECT_EQ(fuchsia::modular::ExecuteStatus::INVALID_COMMAND, result.status);
EXPECT_FALSE(second_command_ran);
}
} // namespace
} // namespace modular