diff options
author | Paul Westbrook <pwestbro@google.com> | 2015-11-01 15:29:33 -0800 |
---|---|---|
committer | Paul Westbrook <pwestbro@google.com> | 2015-11-02 18:55:30 +0000 |
commit | 5a1f600e9d7d26c36b3e22ff0dc0ae9e3b2425fc (patch) | |
tree | 9a3a96971d8c687c1a1976dc9abf49dd8d3c62f2 /src/commands/cloud_command_proxy_unittest.cc | |
parent | 1bc421c9ef13ad855a3f749143fa8c4bc568ef16 (diff) | |
download | libweave-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.cc | 377 |
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(¤t_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 |