Remove Command::Done and add verification of Command state transitions
BUG:23906692
Change-Id: I1934ad70e1918cc5b40ac07ee1e1ccfee406867e
Reviewed-on: https://weave-review.googlesource.com/1259
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/libweave/src/commands/cloud_command_proxy_unittest.cc b/libweave/src/commands/cloud_command_proxy_unittest.cc
index e1d96b0..dbff905 100644
--- a/libweave/src/commands/cloud_command_proxy_unittest.cc
+++ b/libweave/src/commands/cloud_command_proxy_unittest.cc
@@ -147,14 +147,14 @@
TEST_F(CloudCommandProxyTest, ImmediateUpdate) {
const char expected[] = "{'state':'done'}";
EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _, _));
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
}
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_->Done();
+ command_instance_->SetResults({}, nullptr);
// Still no command update here...
callbacks_.Notify(19);
// Now we should get the update...
@@ -247,7 +247,7 @@
EXPECT_TRUE(command_instance_->SetProgress(
*CreateDictionaryValue("{'status': 'busy'}"), nullptr));
current_state_update_id_ = 22;
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Device state #20 updated.
base::Closure on_success;
@@ -286,7 +286,7 @@
EXPECT_TRUE(command_instance_->SetProgress(
*CreateDictionaryValue("{'status': 'busy'}"), nullptr));
current_state_update_id_ = 22;
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Device state 20-21 updated.
base::Closure on_success;
@@ -315,7 +315,7 @@
EXPECT_TRUE(command_instance_->SetProgress(
*CreateDictionaryValue("{'status': 'busy'}"), nullptr));
current_state_update_id_ = 22;
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Device state 30 updated.
const char expected[] = R"({
@@ -336,7 +336,7 @@
*CreateDictionaryValue("{'status': 'finished'}"), nullptr));
EXPECT_TRUE(command_instance_->SetResults(
*CreateDictionaryValue("{'sum': 30}"), nullptr));
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
const char expected[] = R"({
'progress': {'status':'finished'},
@@ -360,7 +360,7 @@
// 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_->Done();
+ command_instance_->SetResults({}, nullptr);
}
TEST_F(CloudCommandProxyTest, NonEmptyStateChangeQueue) {
@@ -372,7 +372,7 @@
CreateCommandInstance();
// No command updates right now.
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Only when the state #20 is published we should update the command
const char expected[] = "{'state':'done'}";
diff --git a/libweave/src/commands/command_instance.cc b/libweave/src/commands/command_instance.cc
index 45c376d..4701bb6 100644
--- a/libweave/src/commands/command_instance.cc
+++ b/libweave/src/commands/command_instance.cc
@@ -38,10 +38,21 @@
{CommandOrigin::kCloud, "cloud"},
};
-void ReportDestroyedError(ErrorPtr* error) {
+bool ReportDestroyedError(ErrorPtr* error) {
Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
errors::commands::kCommandDestroyed,
"Command has been destroyed");
+ return false;
+}
+
+bool ReportInvalidStateTransition(ErrorPtr* error,
+ CommandStatus from,
+ CommandStatus to) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidState,
+ "State switch impossible: '%s' -> '%s'",
+ EnumToString(from).c_str(), EnumToString(to).c_str());
+ return false;
}
} // namespace
@@ -97,12 +108,14 @@
return TypedValueToJson(results_);
}
+const Error* CommandInstance::GetError() const {
+ return error_.get();
+}
+
bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
ErrorPtr* error) {
- if (!command_definition_) {
- ReportDestroyedError(error);
- return false;
- }
+ if (!command_definition_)
+ return ReportDestroyedError(error);
ObjectPropType obj_prop_type;
obj_prop_type.SetObjectSchema(command_definition_->GetProgress()->Clone());
@@ -111,20 +124,21 @@
return false;
// Change status even if progress unchanged, e.g. 0% -> 0%.
- SetStatus(CommandStatus::kInProgress);
+ if (!SetStatus(CommandStatus::kInProgress, error))
+ return false;
+
if (obj != progress_) {
progress_ = obj;
FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
}
+
return true;
}
bool CommandInstance::SetResults(const base::DictionaryValue& results,
ErrorPtr* error) {
- if (!command_definition_) {
- ReportDestroyedError(error);
- return false;
- }
+ if (!command_definition_)
+ return ReportDestroyedError(error);
ObjectPropType obj_prop_type;
obj_prop_type.SetObjectSchema(command_definition_->GetResults()->Clone());
@@ -136,7 +150,16 @@
results_ = obj;
FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
}
- return true;
+ // Change status even if result is unchanged.
+ bool result = SetStatus(CommandStatus::kDone, error);
+ RemoveFromQueue();
+ // The command will be destroyed after that, so do not access any members.
+ return result;
+}
+
+bool CommandInstance::SetError(const Error* command_error, ErrorPtr* error) {
+ error_ = command_error ? command_error->Clone() : nullptr;
+ return SetStatus(CommandStatus::kError, error);
}
namespace {
@@ -267,31 +290,45 @@
observers_.RemoveObserver(observer);
}
-void CommandInstance::Abort(const Error* error) {
- if (error)
- error_ = error->Clone();
- SetStatus(CommandStatus::kAborted);
- RemoveFromQueue();
- // The command will be destroyed after that, so do not access any members.
+bool CommandInstance::Pause(ErrorPtr* error) {
+ return SetStatus(CommandStatus::kPaused, error);
}
-void CommandInstance::Cancel() {
- SetStatus(CommandStatus::kCancelled);
+bool CommandInstance::Abort(const Error* command_error, ErrorPtr* error) {
+ error_ = command_error ? command_error->Clone() : nullptr;
+ bool result = SetStatus(CommandStatus::kAborted, error);
RemoveFromQueue();
// The command will be destroyed after that, so do not access any members.
+ return result;
}
-void CommandInstance::Done() {
- SetStatus(CommandStatus::kDone);
+bool CommandInstance::Cancel(ErrorPtr* error) {
+ bool result = SetStatus(CommandStatus::kCancelled, error);
RemoveFromQueue();
// The command will be destroyed after that, so do not access any members.
+ return result;
}
-void CommandInstance::SetStatus(CommandStatus status) {
- if (status != status_) {
- status_ = status;
- FOR_EACH_OBSERVER(Observer, observers_, OnStatusChanged());
+bool CommandInstance::SetStatus(CommandStatus status, ErrorPtr* error) {
+ if (status == status_)
+ return true;
+ if (status == CommandStatus::kQueued)
+ return ReportInvalidStateTransition(error, status_, status);
+ switch (status_) {
+ case CommandStatus::kDone:
+ case CommandStatus::kCancelled:
+ case CommandStatus::kAborted:
+ case CommandStatus::kExpired:
+ return ReportInvalidStateTransition(error, status_, status);
+ case CommandStatus::kQueued:
+ case CommandStatus::kInProgress:
+ case CommandStatus::kPaused:
+ case CommandStatus::kError:
+ break;
}
+ status_ = status;
+ FOR_EACH_OBSERVER(Observer, observers_, OnStatusChanged());
+ return true;
}
void CommandInstance::RemoveFromQueue() {
diff --git a/libweave/src/commands/command_instance.h b/libweave/src/commands/command_instance.h
index 0bc3c0f..e8a838d 100644
--- a/libweave/src/commands/command_instance.h
+++ b/libweave/src/commands/command_instance.h
@@ -59,13 +59,15 @@
std::unique_ptr<base::DictionaryValue> GetParameters() const override;
std::unique_ptr<base::DictionaryValue> GetProgress() const override;
std::unique_ptr<base::DictionaryValue> GetResults() const override;
+ const Error* GetError() const override;
bool SetProgress(const base::DictionaryValue& progress,
ErrorPtr* error) override;
bool SetResults(const base::DictionaryValue& results,
ErrorPtr* error) override;
- void Abort(const Error* error) override;
- void Cancel() override;
- void Done() override;
+ bool Pause(ErrorPtr* error) override;
+ bool SetError(const Error* command_error, ErrorPtr* error) override;
+ bool Abort(const Error* command_error, ErrorPtr* error) override;
+ bool Cancel(ErrorPtr* error) override;
// Returns command definition.
const CommandDefinition* GetCommandDefinition() const {
@@ -107,7 +109,7 @@
private:
// Helper function to update the command status.
// Used by Abort(), Cancel(), Done() methods.
- void SetStatus(CommandStatus status);
+ bool SetStatus(CommandStatus status, ErrorPtr* error);
// Helper method that removes this command from the command queue.
// Note that since the command queue owns the lifetime of the command instance
// object, removing a command from the queue will also destroy it.
diff --git a/libweave/src/commands/command_instance_unittest.cc b/libweave/src/commands/command_instance_unittest.cc
index f783ca7..7c2536b 100644
--- a/libweave/src/commands/command_instance_unittest.cc
+++ b/libweave/src/commands/command_instance_unittest.cc
@@ -212,15 +212,37 @@
EXPECT_TRUE(instance->SetResults(*CreateDictionaryValue("{'testResult': 17}"),
nullptr));
- ErrorPtr error;
- Error::AddTo(&error, FROM_HERE, "DOMAIN", "CODE", "MESSAGE");
- instance->Abort(error.get());
-
json->MergeDictionary(CreateDictionaryValue(R"({
'id': 'testId',
'progress': {'progress': 15},
+ 'state': 'done',
+ 'results': {'testResult': 17}
+ })").get());
+
+ auto converted = instance->ToJson();
+ EXPECT_PRED2([](const base::Value& val1,
+ const base::Value& val2) { return val1.Equals(&val2); },
+ *json, *converted);
+}
+
+TEST_F(CommandInstanceTest, ToJsonError) {
+ auto json = CreateDictionaryValue(R"({
+ 'name': 'base.reboot',
+ 'parameters': {}
+ })");
+ auto instance = CommandInstance::FromJson(json.get(), CommandOrigin::kCloud,
+ dict_, nullptr, nullptr);
+ instance->SetID("testId");
+
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "DOMAIN", "CODE", "MESSAGE");
+ instance->Abort(error.get(), nullptr);
+
+ json->MergeDictionary(CreateDictionaryValue(R"({
+ 'id': 'testId',
'state': 'aborted',
- 'results': {'testResult': 17},
+ 'progress': {},
+ 'results': {},
'error': {'code': 'CODE', 'message': 'MESSAGE'}
})").get());
diff --git a/libweave/src/commands/schema_constants.cc b/libweave/src/commands/schema_constants.cc
index f53423f..0b0ba40 100644
--- a/libweave/src/commands/schema_constants.cc
+++ b/libweave/src/commands/schema_constants.cc
@@ -26,6 +26,7 @@
const char kInvalidCommandVisibility[] = "invalid_command_visibility";
const char kInvalidMinimalRole[] = "invalid_minimal_role";
const char kCommandDestroyed[] = "command_destroyed";
+const char kInvalidState[] = "invalid_state";
} // namespace commands
} // namespace errors
diff --git a/libweave/src/commands/schema_constants.h b/libweave/src/commands/schema_constants.h
index 6dded05..6cd3061 100644
--- a/libweave/src/commands/schema_constants.h
+++ b/libweave/src/commands/schema_constants.h
@@ -29,6 +29,7 @@
extern const char kInvalidCommandVisibility[];
extern const char kInvalidMinimalRole[];
extern const char kCommandDestroyed[];
+extern const char kInvalidState[];
} // namespace commands
} // namespace errors