aboutsummaryrefslogtreecommitdiff
path: root/src/commands/cloud_command_proxy_unittest.cc
diff options
context:
space:
mode:
authorPaul Westbrook <pwestbro@google.com>2015-11-01 15:29:33 -0800
committerPaul Westbrook <pwestbro@google.com>2015-11-02 18:55:30 +0000
commit5a1f600e9d7d26c36b3e22ff0dc0ae9e3b2425fc (patch)
tree9a3a96971d8c687c1a1976dc9abf49dd8d3c62f2 /src/commands/cloud_command_proxy_unittest.cc
parent1bc421c9ef13ad855a3f749143fa8c4bc568ef16 (diff)
downloadlibweave-5a1f600e9d7d26c36b3e22ff0dc0ae9e3b2425fc.tar.gz
Remove the unneeded libweave directory
Change-Id: I30fd8c5626cf83da6415ffa14a2019ef43be9916 Reviewed-on: https://weave-review.googlesource.com/1450 Reviewed-by: Paul Westbrook <pwestbro@google.com>
Diffstat (limited to 'src/commands/cloud_command_proxy_unittest.cc')
-rw-r--r--src/commands/cloud_command_proxy_unittest.cc377
1 files changed, 377 insertions, 0 deletions
diff --git a/src/commands/cloud_command_proxy_unittest.cc b/src/commands/cloud_command_proxy_unittest.cc
new file mode 100644
index 0000000..0c04592
--- /dev/null
+++ b/src/commands/cloud_command_proxy_unittest.cc
@@ -0,0 +1,377 @@
+// Copyright 2015 The Weave 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/commands/cloud_command_proxy.h"
+
+#include <memory>
+#include <queue>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <weave/provider/test/fake_task_runner.h>
+
+#include "src/commands/command_dictionary.h"
+#include "src/commands/command_instance.h"
+#include "src/commands/unittest_utils.h"
+#include "src/states/mock_state_change_queue_interface.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+
+namespace weave {
+
+using test::CreateDictionaryValue;
+using test::CreateValue;
+
+namespace {
+
+const char kCmdID[] = "abcd";
+
+MATCHER_P(MatchJson, str, "") {
+ return arg.Equals(CreateValue(str).get());
+}
+
+class MockCloudCommandUpdateInterface : public CloudCommandUpdateInterface {
+ public:
+ MOCK_METHOD3(UpdateCommand,
+ void(const std::string&,
+ const base::DictionaryValue&,
+ const DoneCallback&));
+};
+
+// Test back-off entry that uses the test clock.
+class TestBackoffEntry : public BackoffEntry {
+ public:
+ TestBackoffEntry(const Policy* const policy, base::Clock* clock)
+ : BackoffEntry{policy}, clock_{clock} {
+ creation_time_ = clock->Now();
+ }
+
+ private:
+ // Override from BackoffEntry to use the custom test clock for
+ // the backoff calculations.
+ base::TimeTicks ImplGetTimeNow() const override {
+ return base::TimeTicks::FromInternalValue(clock_->Now().ToInternalValue());
+ }
+
+ base::Clock* clock_;
+ base::Time creation_time_;
+};
+
+class CloudCommandProxyTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Set up the test StateChangeQueue.
+ auto callback = [this](
+ const base::Callback<void(StateChangeQueueInterface::UpdateID)>& call) {
+ return callbacks_.Add(call).release();
+ };
+ EXPECT_CALL(state_change_queue_, MockAddOnStateUpdatedCallback(_))
+ .WillRepeatedly(Invoke(callback));
+ EXPECT_CALL(state_change_queue_, GetLastStateChangeId())
+ .WillRepeatedly(testing::ReturnPointee(&current_state_update_id_));
+
+ // Set up the command schema.
+ auto json = CreateDictionaryValue(R"({
+ 'calc': {
+ 'add': {
+ 'parameters': {
+ 'value1': 'integer',
+ 'value2': 'integer'
+ },
+ 'progress': {
+ 'status' : 'string'
+ },
+ 'results': {
+ 'sum' : 'integer'
+ }
+ }
+ }
+ })");
+ CHECK(json.get());
+ CHECK(command_dictionary_.LoadCommands(*json, nullptr, nullptr))
+ << "Failed to parse test command dictionary";
+
+ CreateCommandInstance();
+ }
+
+ void CreateCommandInstance() {
+ auto command_json = CreateDictionaryValue(R"({
+ 'name': 'calc.add',
+ 'id': 'abcd',
+ 'parameters': {
+ 'value1': 10,
+ 'value2': 20
+ }
+ })");
+ CHECK(command_json.get());
+
+ command_instance_ =
+ CommandInstance::FromJson(command_json.get(), Command::Origin::kCloud,
+ command_dictionary_, nullptr, nullptr);
+ CHECK(command_instance_.get());
+
+ // Backoff - start at 1s and double with each backoff attempt and no jitter.
+ static const BackoffEntry::Policy policy{0, 1000, 2.0, 0.0,
+ 20000, -1, false};
+ std::unique_ptr<TestBackoffEntry> backoff{
+ new TestBackoffEntry{&policy, task_runner_.GetClock()}};
+
+ // Finally construct the CloudCommandProxy we are going to test here.
+ std::unique_ptr<CloudCommandProxy> proxy{new CloudCommandProxy{
+ command_instance_.get(), &cloud_updater_, &state_change_queue_,
+ std::move(backoff), &task_runner_}};
+ // CloudCommandProxy::CloudCommandProxy() subscribe itself to weave::Command
+ // notifications. When weave::Command is being destroyed it sends
+ // ::OnCommandDestroyed() and CloudCommandProxy deletes itself.
+ proxy.release();
+ }
+
+ StateChangeQueueInterface::UpdateID current_state_update_id_{0};
+ base::CallbackList<void(StateChangeQueueInterface::UpdateID)> callbacks_;
+ testing::StrictMock<MockCloudCommandUpdateInterface> cloud_updater_;
+ testing::StrictMock<MockStateChangeQueueInterface> state_change_queue_;
+ testing::StrictMock<provider::test::FakeTaskRunner> task_runner_;
+ std::queue<base::Closure> task_queue_;
+ CommandDictionary command_dictionary_;
+ std::unique_ptr<CommandInstance> command_instance_;
+};
+
+} // anonymous namespace
+
+TEST_F(CloudCommandProxyTest, ImmediateUpdate) {
+ const char expected[] = "{'state':'done'}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
+ command_instance_->Complete({}, nullptr);
+ task_runner_.RunOnce();
+}
+
+TEST_F(CloudCommandProxyTest, DelayedUpdate) {
+ // Simulate that the current device state has changed.
+ current_state_update_id_ = 20;
+ // No command update is expected here.
+ command_instance_->Complete({}, nullptr);
+ // Still no command update here...
+ callbacks_.Notify(19);
+ // Now we should get the update...
+ const char expected[] = "{'state':'done'}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
+ callbacks_.Notify(20);
+}
+
+TEST_F(CloudCommandProxyTest, InFlightRequest) {
+ // SetProgress causes two consecutive updates:
+ // state=inProgress
+ // progress={...}
+ // The first state update is sent immediately, the second should be delayed.
+ DoneCallback callback;
+ EXPECT_CALL(
+ cloud_updater_,
+ UpdateCommand(
+ kCmdID,
+ MatchJson("{'state':'inProgress', 'progress':{'status':'ready'}}"),
+ _))
+ .WillOnce(SaveArg<2>(&callback));
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
+
+ task_runner_.RunOnce();
+}
+
+TEST_F(CloudCommandProxyTest, CombineMultiple) {
+ // Simulate that the current device state has changed.
+ current_state_update_id_ = 20;
+ // SetProgress causes two consecutive updates:
+ // state=inProgress
+ // progress={...}
+ // Both updates will be held until device state is updated.
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
+
+ // Now simulate the device state updated. Both updates should come in one
+ // request.
+ const char expected[] = R"({
+ 'progress': {'status':'ready'},
+ 'state':'inProgress'
+ })";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
+ callbacks_.Notify(20);
+}
+
+TEST_F(CloudCommandProxyTest, RetryFailed) {
+ DoneCallback callback;
+
+ const char expect[] =
+ "{'state':'inProgress', 'progress': {'status': 'ready'}}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect), _))
+ .Times(3)
+ .WillRepeatedly(SaveArg<2>(&callback));
+ auto started = task_runner_.GetClock()->Now();
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
+ task_runner_.Run();
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "TEST", "TEST", "TEST");
+ callback.Run(error->Clone());
+ task_runner_.Run();
+ EXPECT_GE(task_runner_.GetClock()->Now() - started,
+ base::TimeDelta::FromSecondsD(0.9));
+
+ callback.Run(error->Clone());
+ task_runner_.Run();
+ EXPECT_GE(task_runner_.GetClock()->Now() - started,
+ base::TimeDelta::FromSecondsD(2.9));
+
+ callback.Run(nullptr);
+ task_runner_.Run();
+ EXPECT_GE(task_runner_.GetClock()->Now() - started,
+ base::TimeDelta::FromSecondsD(2.9));
+}
+
+TEST_F(CloudCommandProxyTest, GateOnStateUpdates) {
+ current_state_update_id_ = 20;
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
+ current_state_update_id_ = 21;
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
+ current_state_update_id_ = 22;
+ command_instance_->Complete({}, nullptr);
+
+ // Device state #20 updated.
+ DoneCallback callback;
+ const char expect1[] = R"({
+ 'progress': {'status':'ready'},
+ 'state':'inProgress'
+ })";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect1), _))
+ .WillOnce(SaveArg<2>(&callback));
+ callbacks_.Notify(20);
+ callback.Run(nullptr);
+
+ // Device state #21 updated.
+ const char expect2[] = "{'progress': {'status':'busy'}}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect2), _))
+ .WillOnce(SaveArg<2>(&callback));
+ callbacks_.Notify(21);
+
+ // Device state #22 updated. Nothing happens here since the previous command
+ // update request hasn't completed yet.
+ callbacks_.Notify(22);
+
+ // Now the command update is complete, send out the patch that happened after
+ // the state #22 was updated.
+ const char expect3[] = "{'state': 'done'}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect3), _))
+ .WillOnce(SaveArg<2>(&callback));
+ callback.Run(nullptr);
+}
+
+TEST_F(CloudCommandProxyTest, CombineSomeStates) {
+ current_state_update_id_ = 20;
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
+ current_state_update_id_ = 21;
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
+ current_state_update_id_ = 22;
+ command_instance_->Complete({}, nullptr);
+
+ // Device state 20-21 updated.
+ DoneCallback callback;
+ const char expect1[] = R"({
+ 'progress': {'status':'busy'},
+ 'state':'inProgress'
+ })";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect1), _))
+ .WillOnce(SaveArg<2>(&callback));
+ callbacks_.Notify(21);
+ callback.Run(nullptr);
+
+ // Device state #22 updated.
+ const char expect2[] = "{'state': 'done'}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect2), _))
+ .WillOnce(SaveArg<2>(&callback));
+ callbacks_.Notify(22);
+ callback.Run(nullptr);
+}
+
+TEST_F(CloudCommandProxyTest, CombineAllStates) {
+ current_state_update_id_ = 20;
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
+ current_state_update_id_ = 21;
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
+ current_state_update_id_ = 22;
+ command_instance_->Complete({}, nullptr);
+
+ // Device state 30 updated.
+ const char expected[] = R"({
+ 'progress': {'status':'busy'},
+ 'state':'done'
+ })";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
+ callbacks_.Notify(30);
+}
+
+TEST_F(CloudCommandProxyTest, CoalesceUpdates) {
+ current_state_update_id_ = 20;
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
+ EXPECT_TRUE(command_instance_->SetProgress(
+ *CreateDictionaryValue("{'status': 'finished'}"), nullptr));
+ EXPECT_TRUE(command_instance_->Complete(*CreateDictionaryValue("{'sum': 30}"),
+ nullptr));
+
+ const char expected[] = R"({
+ 'progress': {'status':'finished'},
+ 'results': {'sum':30},
+ 'state':'done'
+ })";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
+ callbacks_.Notify(30);
+}
+
+TEST_F(CloudCommandProxyTest, EmptyStateChangeQueue) {
+ // Assume the device state update queue was empty and was at update ID 20.
+ current_state_update_id_ = 20;
+
+ // Recreate the command instance and proxy with the new state change queue.
+ CreateCommandInstance();
+
+ // Empty queue will immediately call back with the state change notification.
+ callbacks_.Notify(20);
+
+ // As soon as we change the command, the update to the server should be sent.
+ const char expected[] = "{'state':'done'}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
+ command_instance_->Complete({}, nullptr);
+ task_runner_.RunOnce();
+}
+
+TEST_F(CloudCommandProxyTest, NonEmptyStateChangeQueue) {
+ // Assume the device state update queue was NOT empty when the command
+ // instance was created.
+ current_state_update_id_ = 20;
+
+ // Recreate the command instance and proxy with the new state change queue.
+ CreateCommandInstance();
+
+ // No command updates right now.
+ command_instance_->Complete({}, nullptr);
+
+ // Only when the state #20 is published we should update the command
+ const char expected[] = "{'state':'done'}";
+ EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
+ callbacks_.Notify(20);
+}
+
+} // namespace weave