create generic test device for multiple traits testing.

As part of testing multiple traits using libweave API,
creating a test_schema which can hold multiple traits, and
these multiple traits will be used in one single test device,
and this test device will be used in test cases for test
libweave API.

Change-Id: Ia68e8aa9d43b927f41c7961e62d8ab78c46a85c8
Reviewed-on: https://weave-review.googlesource.com/3068
Reviewed-by: Alex Vakulenko <avakulenko@google.com>
diff --git a/Makefile b/Makefile
index e348fd8..965fd85 100644
--- a/Makefile
+++ b/Makefile
@@ -76,7 +76,7 @@
 out/$(BUILD_MODE)/libweave.so : out/$(BUILD_MODE)/libweave_common.a
 	$(CXX) -shared -Wl,-soname=libweave.so -o $@ -Wl,--whole-archive $^ -Wl,--no-whole-archive -lcrypto -lexpat -lpthread -lrt
 
-include cross.mk file_lists.mk third_party/third_party.mk examples/examples.mk tests.mk
+include cross.mk file_lists.mk third_party/third_party.mk examples/examples.mk tests.mk tests_schema/tests_schema.mk
 
 ###
 # src/
@@ -94,7 +94,7 @@
 all-libs : out/$(BUILD_MODE)/libweave.so
 all-tests : out/$(BUILD_MODE)/libweave_exports_testrunner out/$(BUILD_MODE)/libweave_testrunner
 
-all : all-libs all-examples all-tests
+all : all-libs all-examples all-tests all-testdevices
 
 clean :
 	rm -rf out
diff --git a/tests_schema/daemon/testdevice/standard_traits.h b/tests_schema/daemon/testdevice/standard_traits.h
new file mode 100644
index 0000000..354c783
--- /dev/null
+++ b/tests_schema/daemon/testdevice/standard_traits.h
@@ -0,0 +1,216 @@
+// Copyright 2016 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.
+
+namespace standardtraits {
+const char kTraits[] = R"({
+  "lock": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "lockedState": {
+            "type": "string",
+            "enum": [ "locked", "unlocked" ]
+          }
+        },
+        "errors": [ "jammed", "lockingNotSupported" ]
+      }
+    },
+    "state": {
+      "lockedState": {
+        "type": "string",
+        "enum": [ "locked", "unlocked", "partiallyLocked" ],
+        "isRequired": true
+      },
+      "isLockingSupported": {
+        "type": "boolean",
+        "isRequired": true
+      }
+    }
+  },
+  "onOff": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "state": {
+            "type": "string",
+            "enum": [ "on", "off" ]
+          }
+        }
+      }
+    },
+    "state": {
+      "state": {
+        "type": "string",
+        "enum": [ "on", "off" ],
+        "isRequired": true
+      }
+    }
+  },
+  "brightness": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "brightness": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      }
+    },
+    "state": {
+      "brightness": {
+        "isRequired": true,
+        "type": "number",
+        "minimum": 0.0,
+        "maximum": 1.0
+      }
+    }
+  },
+  "colorTemp": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "colorTemp": {
+            "type": "integer"
+          }
+        }
+      }
+    },
+    "state": {
+      "colorTemp": {
+        "isRequired": true,
+        "type": "integer"
+      },
+      "minColorTemp": {
+        "isRequired": true,
+        "type": "integer"
+      },
+      "maxColorTemp": {
+        "isRequired": true,
+        "type": "integer"
+      }
+    }
+  },
+  "colorXy": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "colorSetting": {
+            "type": "object",
+            "required": [
+              "colorX",
+              "colorY"
+            ],
+            "properties": {
+              "colorX": {
+                "type": "number",
+                "minimum": 0.0,
+                "maximum": 1.0
+              },
+              "colorY": {
+                "type": "number",
+                "minimum": 0.0,
+                "maximum": 1.0
+              }
+            },
+            "additionalProperties": false
+          }
+        },
+        "errors": ["colorOutOfRange"]
+      }
+    },
+    "state": {
+      "colorSetting": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      },
+      "colorCapRed": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      },
+      "colorCapGreen": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      },
+      "colorCapBlue": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      }
+    }
+  }
+})";
+
+const char kDefaultState[] = R"({
+  "lock":{"isLockingSupported": true},
+  "onOff":{"state": "on"},
+  "brightness":{"brightness": 0.0},
+  "colorTemp":{"colorTemp": 0},
+    "colorXy": {
+    "colorSetting": {"colorX": 0.0, "colorY": 0.0},
+    "colorCapRed":  {"colorX": 0.674, "colorY": 0.322},
+    "colorCapGreen":{"colorX": 0.408, "colorY": 0.517},
+    "colorCapBlue": {"colorX": 0.168, "colorY": 0.041}
+  }
+})";
+
+const char kComponent[] = "testdevice";
+}  // namespace jsontraits
diff --git a/tests_schema/daemon/testdevice/testdevice.cc b/tests_schema/daemon/testdevice/testdevice.cc
new file mode 100644
index 0000000..7dea5ac
--- /dev/null
+++ b/tests_schema/daemon/testdevice/testdevice.cc
@@ -0,0 +1,277 @@
+// 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 <algorithm>
+#include <string>
+#include <typeinfo>
+
+#include "examples/daemon/common/daemon.h"
+#include "tests_schema/daemon/testdevice/standard_traits.h"
+
+#include <weave/device.h>
+#include <weave/enum_to_string.h>
+
+#include <base/bind.h>
+#include <base/memory/weak_ptr.h>
+
+namespace weave {
+namespace lockstate {
+enum class LockState { kUnlocked, kLocked, kPartiallyLocked };
+
+const weave::EnumToStringMap<LockState>::Map kLockMapMethod[] = {
+    {LockState::kLocked, "locked"},
+    {LockState::kUnlocked, "unlocked"},
+    {LockState::kPartiallyLocked, "partiallyLocked"}};
+}  // namespace lockstate
+
+template <>
+EnumToStringMap<lockstate::LockState>::EnumToStringMap()
+    : EnumToStringMap(lockstate::kLockMapMethod) {}
+}  // namespace weave
+
+// TestDeviceHandler is a command handler example that shows
+// how to handle commands for a Weave testdevice.
+class TestDeviceHandler {
+ public:
+  TestDeviceHandler() = default;
+  void Register(weave::Device* device) {
+    device_ = device;
+
+    device->AddTraitDefinitionsFromJson(standardtraits::kTraits);
+    CHECK(device->AddComponent(
+        standardtraits::kComponent,
+        {"lock", "onOff", "brightness", "colorTemp", "colorXy"}, nullptr));
+    CHECK(device->SetStatePropertiesFromJson(
+        standardtraits::kComponent, standardtraits::kDefaultState, nullptr));
+
+    UpdateTestDeviceState();
+
+    device->AddCommandHandler(standardtraits::kComponent, "onOff.setConfig",
+                              base::Bind(&TestDeviceHandler::OnOnOffSetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(standardtraits::kComponent, "lock.setConfig",
+                              base::Bind(&TestDeviceHandler::OnLockSetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(
+        standardtraits::kComponent, "brightness.setConfig",
+        base::Bind(&TestDeviceHandler::OnBrightnessSetConfig,
+                   weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(
+        standardtraits::kComponent, "colorTemp.setConfig",
+        base::Bind(&TestDeviceHandler::OnColorTempSetConfig,
+                   weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(standardtraits::kComponent, "colorXy.setConfig",
+                              base::Bind(&TestDeviceHandler::OnColorXySetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+  }
+
+ private:
+  void OnLockSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    std::string requested_state;
+    if (params.GetString("lockedState", &requested_state)) {
+      LOG(INFO) << cmd->GetName() << " state: " << requested_state;
+
+      weave::lockstate::LockState new_lock_status;
+
+      if (!weave::StringToEnum(requested_state, &new_lock_status)) {
+        // Invalid lock state was specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (new_lock_status != lock_state_) {
+        lock_state_ = new_lock_status;
+
+        LOG(INFO) << "Lock is now: " << requested_state;
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+    AbortCommand(cmd);
+  }
+
+  void OnBrightnessSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    double brightness_value = 0.0;
+    if (params.GetDouble("brightness", &brightness_value)) {
+      LOG(INFO) << cmd->GetName() << " brightness: " << brightness_value;
+
+      if (brightness_value < 0.0 || brightness_value > 1.0) {
+        // Invalid brightness range value is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (brightness_state_ != brightness_value) {
+        brightness_state_ = brightness_value;
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+    AbortCommand(cmd);
+  }
+
+  void OnOnOffSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    std::string requested_state;
+    if (params.GetString("state", &requested_state)) {
+      LOG(INFO) << cmd->GetName() << " state: " << requested_state;
+
+      std::string temp_state = requested_state;
+      std::transform(temp_state.begin(), temp_state.end(), temp_state.begin(),
+                     ::toupper);
+      if (temp_state != "ON" && temp_state != "OFF") {
+        // Invalid OnOff state is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      bool new_light_status = requested_state == "on";
+      if (new_light_status != light_status_) {
+        light_status_ = new_light_status;
+        LOG(INFO) << "Light is now: " << (light_status_ ? "ON" : "OFF");
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+    AbortCommand(cmd);
+  }
+
+  void OnColorTempSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+
+    const auto& params = cmd->GetParameters();
+    int32_t color_temp = 0;
+    if (params.GetInteger("colorTemp", &color_temp)) {
+      LOG(INFO) << cmd->GetName() << " colorTemp: " << color_temp;
+
+      if (color_temp < 0.0 || color_temp > 1.0) {
+        // Invalid color_temp value is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (color_temp != color_temp_) {
+        color_temp_ = color_temp;
+
+        LOG(INFO) << "color_Temp is now: " << color_temp_;
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+
+    AbortCommand(cmd);
+  }
+
+  void OnColorXySetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    const base::DictionaryValue* colorXy = nullptr;
+    if (params.GetDictionary("colorSetting", &colorXy)) {
+      bool updateState = false;
+      double X = 0.0;
+      double Y = 0.0;
+      if (colorXy->GetDouble("colorX", &X)) {
+        color_X_ = X;
+        updateState = true;
+      }
+
+      if (colorXy->GetDouble("colorY", &Y)) {
+        color_Y_ = Y;
+        updateState = true;
+      }
+
+      if ((color_X_ < 0.0 || color_Y_ > 1.0) ||
+          (color_Y_ < 0.0 || color_Y_ > 1.0)) {
+        // Invalid color range value is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (updateState)
+        UpdateTestDeviceState();
+
+      cmd->Complete({}, nullptr);
+      return;
+    }
+
+    AbortCommand(cmd);
+  }
+
+  void UpdateTestDeviceState() {
+    std::string updated_state = weave::EnumToString(lock_state_);
+    device_->SetStateProperty(standardtraits::kComponent, "lock.lockedState",
+                              base::StringValue{updated_state}, nullptr);
+    base::DictionaryValue state;
+    state.SetString("onOff.state", light_status_ ? "on" : "off");
+    state.SetDouble("brightness.brightness", brightness_state_);
+    state.SetInteger("colorTemp.minColorTemp", color_temp_min_value_);
+    state.SetInteger("colorTemp.maxColorTemp", color_temp_max_value_);
+    state.SetInteger("colorTemp.colorTemp", color_temp_);
+
+    std::unique_ptr<base::DictionaryValue> colorXy(new base::DictionaryValue());
+    colorXy->SetDouble("colorX", color_X_);
+    colorXy->SetDouble("colorY", color_Y_);
+    state.Set("colorXy.colorSetting", std::move(colorXy));
+
+    device_->SetStateProperties(standardtraits::kComponent, state, nullptr);
+  }
+
+  void AbortCommand(std::shared_ptr<weave::Command>& cmd) {
+    weave::ErrorPtr error;
+    weave::Error::AddTo(&error, FROM_HERE, "invalidParameterValue",
+                        "Invalid parameters");
+    cmd->Abort(error.get(), nullptr);
+  }
+
+  weave::Device* device_{nullptr};
+
+  // Simulate the state of the testdevice.
+  weave::lockstate::LockState lock_state_{weave::lockstate::LockState::kLocked};
+  bool light_status_{false};
+  double brightness_state_{0.0};
+  int32_t color_temp_{0};
+  int32_t color_temp_min_value_{0};
+  int32_t color_temp_max_value_{1};
+  double color_X_{0.0};
+  double color_Y_{0.0};
+  base::WeakPtrFactory<TestDeviceHandler> weak_ptr_factory_{this};
+};
+
+int main(int argc, char** argv) {
+  Daemon::Options opts;
+  opts.model_id = "AOAAA";
+  if (!opts.Parse(argc, argv)) {
+    Daemon::Options::ShowUsage(argv[0]);
+    return 1;
+  }
+  Daemon daemon{opts};
+  TestDeviceHandler handler;
+  handler.Register(daemon.GetDevice());
+  daemon.Run();
+  return 0;
+}
diff --git a/tests_schema/tests_schema.mk b/tests_schema/tests_schema.mk
new file mode 100644
index 0000000..19bdb26
--- /dev/null
+++ b/tests_schema/tests_schema.mk
@@ -0,0 +1,67 @@
+# Copyright 2016 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.
+
+###
+# examples
+
+tests_schema_provider_obj_files := $(EXAMPLES_PROVIDER_SRC_FILES:%.cc=out/$(BUILD_MODE)/%.o)
+
+USE_INTERNAL_LIBEVHTP ?= 1
+
+ifeq (1, $(USE_INTERNAL_LIBEVHTP))
+LIBEVHTP_INCLUDES = -Ithird_party/libevhtp -I$(dir $(third_party_libevhtp_header))
+LIBEVHTP_HEADERS = $(third_party_libevhtp_header)
+else
+LIBEVHTP_INCLUDES =
+LIBEVHTP_HEADERS =
+endif
+
+$(tests_schema_provider_obj_files) : $(LIBEVHTP_HEADERS)
+$(tests_schema_provider_obj_files) : INCLUDES += $(LIBEVHTP_INCLUDES)
+$(tests_schema_provider_obj_files) : out/$(BUILD_MODE)/%.o : %.cc
+	mkdir -p $(dir $@)
+	$(CXX) $(DEFS_$(BUILD_MODE)) $(INCLUDES) $(CFLAGS) $(CFLAGS_$(BUILD_MODE)) $(CFLAGS_CC) -c -o $@ $<
+
+out/$(BUILD_MODE)/examples_provider.a : $(tests_schema_provider_obj_files)
+	rm -f $@
+	$(AR) crsT $@ $^
+
+TESTS_SCHEMA_DAEMON_SRC_FILES := \
+	tests_schema/daemon/testdevice/testdevice.cc
+
+tests_schema_daemon_obj_files := $(TESTS_SCHEMA_DAEMON_SRC_FILES:%.cc=out/$(BUILD_MODE)/%.o)
+
+$(tests_schema_daemon_obj_files) : $(LIBEVHTP_HEADERS)
+$(tests_schema_daemon_obj_files) : INCLUDES += $(LIBEVHTP_INCLUDES)
+$(tests_schema_daemon_obj_files) : out/$(BUILD_MODE)/%.o : %.cc
+	mkdir -p $(dir $@)
+	$(CXX) $(DEFS_$(BUILD_MODE)) $(INCLUDES) $(CFLAGS) $(CFLAGS_$(BUILD_MODE)) $(CFLAGS_CC) -c -o $@ $<
+
+daemon_common_flags := \
+	-Wl,-rpath=out/$(BUILD_MODE)/ \
+	-levent \
+	-levent_openssl \
+	-lpthread \
+	-lavahi-common \
+	-lavahi-client \
+	-lexpat \
+	-lcurl \
+	-lssl \
+	-lcrypto
+
+daemon_deps := out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so
+
+ifeq (1, $(USE_INTERNAL_LIBEVHTP))
+daemon_deps += $(third_party_libevhtp_lib)
+else
+daemon_common_flags += -levhtp
+endif
+
+out/$(BUILD_MODE)/weave_daemon_testdevice : out/$(BUILD_MODE)/tests_schema/daemon/testdevice/testdevice.o $(daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags)
+
+all-testdevices : out/$(BUILD_MODE)/weave_daemon_testdevice
+
+.PHONY : all-testdevices
+