From 6c8fc3a1a183e6afae93278c6b600b53252f5d0a Mon Sep 17 00:00:00 2001 From: Peter Gebruers Date: Mon, 4 Nov 2019 20:43:29 +0100 Subject: [PATCH] Add GetValueEndPoint() to Manager Introduces helper function: uint8 Manager::GetValueEndPoint(ValueID const &_id) The purpose is - To allow printing of "End Point" for diagnosis. End Point is what is used in conversation with a device, not "Instance". - To allow selection of Values with certain End Point for use in "Multi Channel Association". This class does not accept "Instance" Adds tests to cpp/test/Manager_test.cpp - verifies a (fake) mapping and tests exception thrown. Adds "TestHelper" as a friend to allow testing of existing OZW classes without refactoring and without having to start a "real" Manager and Driver (which would have its merits but would be to involved and heavy) --- cpp/src/Driver.h | 5 ++ cpp/src/Manager.cpp | 43 ++++++++++++++ cpp/src/Manager.h | 15 +++++ cpp/src/Node.h | 7 +++ cpp/src/Options.h | 7 +++ cpp/test/Manager_test.cpp | 100 +++++++++++++++++++++++++++++++++ cpp/test/TestHelper.cpp | 114 ++++++++++++++++++++++++++++++++++++++ cpp/test/TestHelper.h | 62 +++++++++++++++++++++ 8 files changed, 353 insertions(+) create mode 100644 cpp/test/Manager_test.cpp create mode 100644 cpp/test/TestHelper.cpp create mode 100644 cpp/test/TestHelper.h diff --git a/cpp/src/Driver.h b/cpp/src/Driver.h index a41fb20baa..04e7796082 100644 --- a/cpp/src/Driver.h +++ b/cpp/src/Driver.h @@ -77,6 +77,10 @@ namespace OpenZWave class Msg; class TimerThread; } + namespace Testing + { + class TestHelper; + } /** \brief The Driver class handles communication between OpenZWave * and a device attached via a serial port (typically a controller). @@ -103,6 +107,7 @@ namespace OpenZWave friend class Internal::Msg; friend class Internal::ManufacturerSpecificDB; friend class TimerThread; + friend class Testing::TestHelper; //----------------------------------------------------------------------------- // Controller Interfaces diff --git a/cpp/src/Manager.cpp b/cpp/src/Manager.cpp index f6966b24c0..a25d0860a9 100644 --- a/cpp/src/Manager.cpp +++ b/cpp/src/Manager.cpp @@ -2518,6 +2518,49 @@ bool Manager::GetValueListValues(ValueID const& _id, vector* o_value) return res; } +uint8 Manager::GetValueEndPoint(ValueID const &_id) +{ + if (Driver *driver = GetDriver(_id.GetHomeId())) + { + // Need to lock and unlock nodes before calling driver->GetValue + Internal::LockGuard LG(driver->m_nodeMutex); + + // We don't actually need the Value, everything we need is in _id + // But we call GetValue to check if it is valid. + if (auto v = driver->GetValue(_id)) + { + // Because GetValue worked, the Node ID and CC have to be valid + // So theoretically GetNode and GetCommandClass cannot fail. + // Unless... there is some multithread issue somewhere else. + if (auto node = driver->GetNode(_id.GetNodeId())) + { + auto cc = node->GetCommandClass(_id.GetCommandClassId()); + if (cc) + { + return cc->GetEndPoint(_id.GetInstance()); + } + else + { + OZW_ERROR(OZWException::OZWEXCEPTION_INVALID_VALUEID, "Invalid ValueID passed to GetValueEndPoint: node does not have CommandClass"); + } + } + else + { + OZW_ERROR(OZWException::OZWEXCEPTION_INVALID_VALUEID, "Invalid ValueID passed to GetValueEndPoint: node does exist"); + } + } + else + { + OZW_ERROR(OZWException::OZWEXCEPTION_INVALID_VALUEID, "Invalid ValueID passed to GetValueEndPoint"); + } + } + else + { + // There is no real "else" part because either we get a driver or GetDriver throws an exception + return 0; + } +} + //----------------------------------------------------------------------------- // // Gets a value's scale as a uint8 diff --git a/cpp/src/Manager.h b/cpp/src/Manager.h index 3abe392f53..ab82998e99 100644 --- a/cpp/src/Manager.h +++ b/cpp/src/Manager.h @@ -59,6 +59,11 @@ namespace OpenZWave class Node; class Notification; + namespace Testing + { + class TestHelper; + } + /** \brief * The main public interface to OpenZWave. * @@ -114,6 +119,7 @@ namespace OpenZWave friend class Internal::VC::Value; friend class Internal::VC::ValueStore; friend class Internal::Msg; + friend class Testing::TestHelper; public: typedef void (*pfnOnNotification_t)(Notification const* _pNotification, void* _context); @@ -1260,6 +1266,15 @@ namespace OpenZWave */ bool GetValueListValues(ValueID const& _id, vector* o_value); + /** + * \brief Gets the End Point of a ValueID + * \param _id The unique identifier of the value. + * \return The End Point of _id + * \throws OZWException with Type OZWException::OZWEXCEPTION_INVALID_VALUEID if the ValueID is invalid + * \throws OZWException with Type OZWException::OZWEXCEPTION_INVALID_HOMEID if the Driver cannot be found + */ + uint8 GetValueEndPoint(ValueID const& _id); + /** * \brief Gets a float value's precision. * \param _id The unique identifier of the value. diff --git a/cpp/src/Node.h b/cpp/src/Node.h index 374b7bc49b..d9cda6e3b3 100644 --- a/cpp/src/Node.h +++ b/cpp/src/Node.h @@ -72,6 +72,12 @@ namespace OpenZWave class ProductDescriptor; class ManufacturerSpecificDB; } + + namespace Testing + { + class TestHelper; + } + class Driver; class Group; @@ -97,6 +103,7 @@ namespace OpenZWave friend class Internal::CC::Version; friend class Internal::CC::ZWavePlusInfo; friend class Internal::ManufacturerSpecificDB; + friend class Testing::TestHelper; //----------------------------------------------------------------------------- // Construction diff --git a/cpp/src/Options.h b/cpp/src/Options.h index 4ceae15fdf..7eca04a485 100644 --- a/cpp/src/Options.h +++ b/cpp/src/Options.h @@ -36,6 +36,11 @@ namespace OpenZWave { + namespace Testing + { + class TestHelper; + } + /** \brief Manages library options read from XML files or the command line. * * A class that manages program options read from XML files or the command line. @@ -65,6 +70,8 @@ namespace OpenZWave */ class OPENZWAVE_EXPORT Options { + friend class Testing::TestHelper; + public: enum OptionType { diff --git a/cpp/test/Manager_test.cpp b/cpp/test/Manager_test.cpp new file mode 100644 index 0000000000..c209918d2c --- /dev/null +++ b/cpp/test/Manager_test.cpp @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// +// Manager_test.cpp +// +// Test Framework for Manager Stuff +// +// Copyright (c) 2019 Peter Gebruers +// +// Based on work Copyrighted (c) 2017 Justin Hammond +// +// SOFTWARE NOTICE AND LICENSE +// +// This file is part of OpenZWave. +// +// OpenZWave is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// OpenZWave is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with OpenZWave. If not, see . +// +//----------------------------------------------------------------------------- + +#include "Manager.h" +#include "TestHelper.h" +#include "gtest/gtest.h" + +namespace OpenZWave +{ +namespace Testing +{ + +TEST_F(TestHelper, GetValueEndPoint) +{ + // Test valid data + EXPECT_EQ( + Manager::Get()->GetValueEndPoint(ValueID(FakeHomeId, FakeNode2Id, ValueID::ValueGenre_Basic, FakeCommandClass, Instance1, FakeValueIndex, ValueID::ValueType_BitSet)), + 0); + + EXPECT_EQ( + Manager::Get()->GetValueEndPoint(ValueID(FakeHomeId, FakeNode2Id, ValueID::ValueGenre_Basic, FakeCommandClass, Instance2, FakeValueIndex, ValueID::ValueType_BitSet)), + 1); + + EXPECT_EQ( + Manager::Get()->GetValueEndPoint(ValueID(FakeHomeId, FakeNode2Id, ValueID::ValueGenre_Basic, FakeCommandClass, Instance3, FakeValueIndex, ValueID::ValueType_BitSet)), + 127); + + // Test *unset* Instance i.e. device does not have Instance + + EXPECT_EQ( + Manager::Get()->GetValueEndPoint(ValueID(FakeHomeId, FakeNode2Id, ValueID::ValueGenre_Basic, FakeCommandClass, Instance4, FakeValueIndex, ValueID::ValueType_BitSet)), + 0); + + // Test exceptions + + // Could compare "full" message but might prove "unstable" because it contains source code and line number + // EXPECT_STREQ(e.what(), "Manager.cpp:403 - InvalidHomeIDError (100) Msg: Invalid HomeId passed to GetDriver"); + + // Cannot use EXPECT_THROW of googletest here because we need to test properties of the exception. + // https://github.com/google/googletest/issues/952 + + try + { + Manager::Get()->GetValueEndPoint(ValueID(0, static_cast(0x01))); + ADD_FAILURE() << "GetValueEndPoint should throw an error, but it did not..."; + } + catch (OZWException &e) + { + EXPECT_EQ(e.GetMsg(), "Invalid HomeId passed to GetDriver"); + EXPECT_EQ(e.GetType(), OZWException::ExceptionType::OZWEXCEPTION_INVALID_HOMEID); + } + catch (...) + { + ADD_FAILURE() << "GetValueEndPoint should throw OZWException but got a different type.\n "; + } + + try + { + Manager::Get()->GetValueEndPoint(ValueID(FakeHomeId, static_cast(0x01))); + ADD_FAILURE() << "GetValueEndPoint should throw an error, but it did not..."; + } + catch (OZWException &e) + { + EXPECT_EQ(e.GetMsg(), "Invalid ValueID passed to GetValueEndPoint"); + EXPECT_EQ(e.GetType(), OZWException::ExceptionType::OZWEXCEPTION_INVALID_VALUEID); + } + catch (...) + { + ADD_FAILURE() << "GetValueEndPoint should throw OZWException but got a different type.\n "; + } +} + +} // namespace Testing +} // namespace OpenZWave \ No newline at end of file diff --git a/cpp/test/TestHelper.cpp b/cpp/test/TestHelper.cpp new file mode 100644 index 0000000000..2613ac1375 --- /dev/null +++ b/cpp/test/TestHelper.cpp @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2019 Peter Gebruers +// +// Based on work Copyrighted (c) 2017 Justin Hammond +// +// SOFTWARE NOTICE AND LICENSE +// +// This file is part of OpenZWave. +// +// OpenZWave is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// OpenZWave is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with OpenZWave. If not, see . +// +//----------------------------------------------------------------------------- + +/* + To get access to private members of Options, Manager and Driver, + this has to be in their header files + + namespace Testing + { + class TestHelper; + } + + friend class Testing::TestHelper; +*/ + +#include "TestHelper.h" +#include "Manager.h" +#include "Options.h" +#include "command_classes/CommandClass.h" + +namespace OpenZWave +{ +namespace Testing +{ + +void TestHelper::SetUp() +{ + // Doing options = new Options then "fix up" the internal + // state of the object creates a "fake object". It is + // much faster, and lighter than calling Options::Create + // because the latter does a lot more. For testing purposes + // we don't need need everything done by Create. + + // The constructor of Options does not seem to set "the singleton" + // while the constructor of Manager does set it... Do this here... + Options::s_instance = new Options("", "", ""); + + // Doing "new Manager" creates a "fake Manager object". + // It is much faster, and lighter than calling Manager::Create + // because the latter does a lot more... Like logging, doing http, load + // config files... Last time I checked, Create took > 1000 ms. + // doing "new Manager" takes a fraction of that. + // A call to Manager::Destroy(); is needed to free the memory + // Manager is a singleton. + new Manager(); + + // You would expect to call Manager::AddDriver("") here but that will start up many + // things we do not need and will ultimately fail to open the port because we do + // not have a port... Instead create an instance with fake controllerPath. + + Driver *driver = new Driver("dummy", Driver::ControllerInterface::ControllerInterface_Serial); + + // Pretend the Manager knows about a certain HomeID by setting m_readyDrivers to this fake driver + Manager::Get()->m_readyDrivers[FakeHomeId] = driver; + + // Pretend node 2 exists + Node *node = new Node(FakeHomeId, FakeNode2Id); + driver->m_nodes[node->GetNodeId()] = node; + + auto cc = node->AddCommandClass(FakeCommandClass); + + if (cc == nullptr) + { + throw std::runtime_error("auto cc = node->AddCommandClass(test_cc) returned a nullptr"); + } + + // Real devices will usually have either Instance 1 -> End Point 1 or 0 + // But for sake of testing we can set anything we like. + cc->SetEndPoint(Instance1, 0); + node->CreateValueString(ValueID::ValueGenre_User, FakeCommandClass, Instance1, FakeValueIndex, "label", "units", false, false, "default", 0); + cc->SetEndPoint(Instance2, 1); + node->CreateValueString(ValueID::ValueGenre_User, FakeCommandClass, Instance2, FakeValueIndex, "label", "units", false, false, "default", 0); + cc->SetEndPoint(Instance3, 127); + node->CreateValueString(ValueID::ValueGenre_User, FakeCommandClass, Instance3, FakeValueIndex, "label", "units", false, false, "default", 0); + + // Set a value, but do not set an endpoint, to test if the map properly initializes to zero + node->CreateValueString(ValueID::ValueGenre_User, FakeCommandClass, Instance4, FakeValueIndex, "label", "units", false, false, "default", 0); +} + +// virtual void TearDown() will be called after each test is run. + +void TestHelper::TearDown() +{ + // Do a reasonable job of cleaning up + // Manager::Get()->RemoveDriver calls the driver's destructor + // That destructor is pretty long and destroys a truckload of objects + Manager::Get()->RemoveDriver("dummy"); + Manager::Destroy(); + Options::Destroy(); +}; + +} // namespace Testing +} // namespace OpenZWave \ No newline at end of file diff --git a/cpp/test/TestHelper.h b/cpp/test/TestHelper.h new file mode 100644 index 0000000000..3d199c714d --- /dev/null +++ b/cpp/test/TestHelper.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// +// Manager_test.cpp +// +// Test Framework for Manager Stuff +// +// Copyright (c) 2019 Peter Gebruers +// +// Based on work Copyrighted (c) 2017 Justin Hammond +// +// SOFTWARE NOTICE AND LICENSE +// +// This file is part of OpenZWave. +// +// OpenZWave is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// OpenZWave is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with OpenZWave. If not, see . +// +//----------------------------------------------------------------------------- +#ifndef TESTING_TESTHELPER_H +#define TESTING_TESTHELPER_H + +#include +#include "gtest/gtest.h" + +namespace OpenZWave +{ +namespace Testing +{ + +// FakeHomeId and FakeNode2Id are used to create "Fake" objects +// when SetUp() is called by the test framework. +// Using these constants improves readability +constexpr uint32_t FakeHomeId = 0x99999999U; +constexpr uint8_t FakeNode2Id = 0x02; +constexpr uint16_t FakeValueIndex = 0x02; +constexpr uint8_t FakeCommandClass = 0x20; + +// We'll often use "instance" in a function call with many parameters +// Using these names improves readability +constexpr uint8_t Instance1 = 0x01; +constexpr uint8_t Instance2 = 0x02; +constexpr uint8_t Instance3 = 0x03; +constexpr uint8_t Instance4 = 0x04; +class TestHelper : public testing::Test +{ +protected: + void SetUp() override; + void TearDown() override; +}; +} // namespace Testing +} // namespace OpenZWave +#endif \ No newline at end of file