Commit 4a7edd72d270f1d1ec0a0c8c4772209742792d78
Committed by
Moritz Wirger
1 parent
cd30bc74
Fix APICache refresh timing, add tests for APICache.
Showing
5 changed files
with
151 additions
and
9 deletions
src/APICache.cpp
| @@ -25,22 +25,35 @@ | @@ -25,22 +25,35 @@ | ||
| 25 | 25 | ||
| 26 | hueplusplus::APICache::APICache( | 26 | hueplusplus::APICache::APICache( |
| 27 | const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh) | 27 | const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh) |
| 28 | - : path(path), | ||
| 29 | - commands(commands), | ||
| 30 | - refreshDuration(refresh), | ||
| 31 | - lastRefresh(std::chrono::steady_clock::duration::zero()) | 28 | + : path(path), commands(commands), refreshDuration(refresh), lastRefresh(std::chrono::steady_clock::duration::zero()) |
| 32 | {} | 29 | {} |
| 33 | 30 | ||
| 34 | -void hueplusplus::APICache::Refresh() { | 31 | +void hueplusplus::APICache::Refresh() |
| 32 | +{ | ||
| 35 | value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO); | 33 | value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO); |
| 34 | + lastRefresh = std::chrono::steady_clock::now(); | ||
| 36 | } | 35 | } |
| 37 | 36 | ||
| 38 | nlohmann::json& hueplusplus::APICache::GetValue() | 37 | nlohmann::json& hueplusplus::APICache::GetValue() |
| 39 | { | 38 | { |
| 40 | - if (std::chrono::steady_clock::now() >= lastRefresh + refreshDuration) | 39 | + using clock = std::chrono::steady_clock; |
| 40 | + // Explicitly check for zero in case refreshDuration is duration::max() | ||
| 41 | + // Negative duration causes overflow check to overflow itself | ||
| 42 | + if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) | ||
| 41 | { | 43 | { |
| 44 | + // No value set yet | ||
| 42 | Refresh(); | 45 | Refresh(); |
| 43 | } | 46 | } |
| 47 | + // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). | ||
| 48 | + // If addition would overflow, do not refresh | ||
| 49 | + else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch()) | ||
| 50 | + { | ||
| 51 | + clock::time_point nextRefresh = lastRefresh + refreshDuration; | ||
| 52 | + if (clock::now() >= nextRefresh) | ||
| 53 | + { | ||
| 54 | + Refresh(); | ||
| 55 | + } | ||
| 56 | + } | ||
| 44 | return value; | 57 | return value; |
| 45 | } | 58 | } |
| 46 | 59 |
test/CMakeLists.txt
| @@ -30,6 +30,7 @@ target_compile_features(gtest PUBLIC cxx_std_14) | @@ -30,6 +30,7 @@ target_compile_features(gtest PUBLIC cxx_std_14) | ||
| 30 | 30 | ||
| 31 | # define all test sources | 31 | # define all test sources |
| 32 | set(TEST_SOURCES | 32 | set(TEST_SOURCES |
| 33 | + test_APICache.cpp | ||
| 33 | test_BaseHttpHandler.cpp | 34 | test_BaseHttpHandler.cpp |
| 34 | test_ExtendedColorHueStrategy.cpp | 35 | test_ExtendedColorHueStrategy.cpp |
| 35 | test_ExtendedColorTemperatureStrategy.cpp | 36 | test_ExtendedColorTemperatureStrategy.cpp |
test/test_APICache.cpp
0 → 100644
| 1 | +/** | ||
| 2 | + \file test_Hue.cpp | ||
| 3 | + Copyright Notice\n | ||
| 4 | + Copyright (C) 2017 Jan Rogall - developer\n | ||
| 5 | + Copyright (C) 2017 Moritz Wirger - developer\n | ||
| 6 | + | ||
| 7 | + This file is part of hueplusplus. | ||
| 8 | + | ||
| 9 | + hueplusplus is free software: you can redistribute it and/or modify | ||
| 10 | + it under the terms of the GNU Lesser General Public License as published by | ||
| 11 | + the Free Software Foundation, either version 3 of the License, or | ||
| 12 | + (at your option) any later version. | ||
| 13 | + | ||
| 14 | + hueplusplus is distributed in the hope that it will be useful, | ||
| 15 | + but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | + GNU Lesser General Public License for more details. | ||
| 18 | + | ||
| 19 | + You should have received a copy of the GNU Lesser General Public License | ||
| 20 | + along with hueplusplus. If not, see <http://www.gnu.org/licenses/>. | ||
| 21 | +**/ | ||
| 22 | + | ||
| 23 | +#include <gtest/gtest.h> | ||
| 24 | + | ||
| 25 | +#include "testhelper.h" | ||
| 26 | + | ||
| 27 | +#include "hueplusplus/APICache.h" | ||
| 28 | +#include "mocks/mock_HttpHandler.h" | ||
| 29 | + | ||
| 30 | +using namespace hueplusplus; | ||
| 31 | + | ||
| 32 | +TEST(APICache, GetRefreshDuration) | ||
| 33 | +{ | ||
| 34 | + auto handler = std::make_shared<MockHttpHandler>(); | ||
| 35 | + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | ||
| 36 | + { | ||
| 37 | + std::chrono::steady_clock::duration refresh = std::chrono::seconds(20); | ||
| 38 | + APICache cache("", commands, refresh); | ||
| 39 | + EXPECT_EQ(refresh, cache.GetRefreshDuration()); | ||
| 40 | + } | ||
| 41 | + { | ||
| 42 | + std::chrono::steady_clock::duration refresh = std::chrono::seconds(0); | ||
| 43 | + APICache cache("", commands, refresh); | ||
| 44 | + EXPECT_EQ(refresh, cache.GetRefreshDuration()); | ||
| 45 | + } | ||
| 46 | + { | ||
| 47 | + std::chrono::steady_clock::duration refresh = std::chrono::steady_clock::duration::max(); | ||
| 48 | + APICache cache("", commands, refresh); | ||
| 49 | + EXPECT_EQ(refresh, cache.GetRefreshDuration()); | ||
| 50 | + } | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +TEST(APICache, Refresh) | ||
| 54 | +{ | ||
| 55 | + using namespace ::testing; | ||
| 56 | + auto handler = std::make_shared<MockHttpHandler>(); | ||
| 57 | + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | ||
| 58 | + | ||
| 59 | + { | ||
| 60 | + std::string path = "/test/abc"; | ||
| 61 | + APICache cache(path, commands, std::chrono::seconds(10)); | ||
| 62 | + EXPECT_CALL(*handler, | ||
| 63 | + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | ||
| 64 | + .WillOnce(Return(nlohmann::json::object())); | ||
| 65 | + cache.Refresh(); | ||
| 66 | + Mock::VerifyAndClearExpectations(handler.get()); | ||
| 67 | + } | ||
| 68 | + { | ||
| 69 | + std::string path = ""; | ||
| 70 | + APICache cache(path, commands, std::chrono::seconds(10)); | ||
| 71 | + EXPECT_CALL(*handler, | ||
| 72 | + GETJson( | ||
| 73 | + "/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | ||
| 74 | + .Times(2) | ||
| 75 | + .WillRepeatedly(Return(nlohmann::json::object())); | ||
| 76 | + cache.Refresh(); | ||
| 77 | + cache.Refresh(); | ||
| 78 | + Mock::VerifyAndClearExpectations(handler.get()); | ||
| 79 | + } | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +TEST(APICache, GetValue) | ||
| 83 | +{ | ||
| 84 | + using namespace ::testing; | ||
| 85 | + auto handler = std::make_shared<MockHttpHandler>(); | ||
| 86 | + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | ||
| 87 | + | ||
| 88 | + // Always refresh | ||
| 89 | + { | ||
| 90 | + std::string path = "/test/abc"; | ||
| 91 | + APICache cache(path, commands, std::chrono::seconds(0)); | ||
| 92 | + nlohmann::json value = { {"a", "b"} }; | ||
| 93 | + EXPECT_CALL(*handler, | ||
| 94 | + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | ||
| 95 | + .Times(2) | ||
| 96 | + .WillRepeatedly(Return(value)); | ||
| 97 | + EXPECT_EQ(value, cache.GetValue()); | ||
| 98 | + EXPECT_EQ(value, cache.GetValue()); | ||
| 99 | + Mock::VerifyAndClearExpectations(handler.get()); | ||
| 100 | + } | ||
| 101 | + // Only refresh once | ||
| 102 | + { | ||
| 103 | + std::string path = "/test/abc"; | ||
| 104 | + APICache cache(path, commands, std::chrono::steady_clock::duration::max()); | ||
| 105 | + nlohmann::json value = { {"a", "b"} }; | ||
| 106 | + EXPECT_CALL(*handler, | ||
| 107 | + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | ||
| 108 | + .WillOnce(Return(value)); | ||
| 109 | + EXPECT_EQ(value, cache.GetValue()); | ||
| 110 | + EXPECT_EQ(value, cache.GetValue()); | ||
| 111 | + Mock::VerifyAndClearExpectations(handler.get()); | ||
| 112 | + } | ||
| 113 | + // No refresh with const | ||
| 114 | + { | ||
| 115 | + std::string path = "/test/abc"; | ||
| 116 | + const APICache cache(path, commands, std::chrono::steady_clock::duration::max()); | ||
| 117 | + nlohmann::json value = { {"a", "b"} }; | ||
| 118 | + EXPECT_CALL(*handler, | ||
| 119 | + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | ||
| 120 | + .Times(0); | ||
| 121 | + EXPECT_EQ(nullptr, cache.GetValue()); | ||
| 122 | + Mock::VerifyAndClearExpectations(handler.get()); | ||
| 123 | + } | ||
| 124 | +} | ||
| 0 | \ No newline at end of file | 125 | \ No newline at end of file |
test/test_Hue.cpp
| @@ -20,8 +20,6 @@ | @@ -20,8 +20,6 @@ | ||
| 20 | along with hueplusplus. If not, see <http://www.gnu.org/licenses/>. | 20 | along with hueplusplus. If not, see <http://www.gnu.org/licenses/>. |
| 21 | **/ | 21 | **/ |
| 22 | 22 | ||
| 23 | -#include <atomic> | ||
| 24 | -#include <iostream> | ||
| 25 | #include <memory> | 23 | #include <memory> |
| 26 | #include <string> | 24 | #include <string> |
| 27 | 25 | ||
| @@ -285,6 +283,9 @@ TEST(Hue, getLight) | @@ -285,6 +283,9 @@ TEST(Hue, getLight) | ||
| 285 | .Times(AtLeast(1)) | 283 | .Times(AtLeast(1)) |
| 286 | .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); | 284 | .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); |
| 287 | 285 | ||
| 286 | + // Refresh cache | ||
| 287 | + test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | ||
| 288 | + | ||
| 288 | // Test when correct data is sent | 289 | // Test when correct data is sent |
| 289 | HueLight test_light_1 = test_bridge.getLight(1); | 290 | HueLight test_light_1 = test_bridge.getLight(1); |
| 290 | EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); | 291 | EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); |
| @@ -479,7 +480,7 @@ TEST(Hue, lightExists) | @@ -479,7 +480,7 @@ TEST(Hue, lightExists) | ||
| 479 | {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}}}; | 480 | {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}}}; |
| 480 | EXPECT_CALL( | 481 | EXPECT_CALL( |
| 481 | *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) | 482 | *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) |
| 482 | - .Times(AtLeast(2)) | 483 | + .Times(AtLeast(1)) |
| 483 | .WillRepeatedly(Return(hue_bridge_state)); | 484 | .WillRepeatedly(Return(hue_bridge_state)); |
| 484 | EXPECT_CALL(*handler, | 485 | EXPECT_CALL(*handler, |
| 485 | GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | 486 | GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) |
test/test_HueLight.cpp
| @@ -54,6 +54,7 @@ protected: | @@ -54,6 +54,7 @@ protected: | ||
| 54 | hue_bridge_state["lights"]["1"]["state"]["alert"] = "none"; | 54 | hue_bridge_state["lights"]["1"]["state"]["alert"] = "none"; |
| 55 | hue_bridge_state["lights"]["1"]["state"]["colormode"] = "ct"; | 55 | hue_bridge_state["lights"]["1"]["state"]["colormode"] = "ct"; |
| 56 | hue_bridge_state["lights"]["1"]["state"]["reachable"] = true; | 56 | hue_bridge_state["lights"]["1"]["state"]["reachable"] = true; |
| 57 | + hue_bridge_state["lights"]["1"]["state"]["effect"] = "none"; | ||
| 57 | hue_bridge_state["lights"]["1"]["swupdate"] = nlohmann::json::object(); | 58 | hue_bridge_state["lights"]["1"]["swupdate"] = nlohmann::json::object(); |
| 58 | hue_bridge_state["lights"]["1"]["swupdate"]["state"] = "noupdates"; | 59 | hue_bridge_state["lights"]["1"]["swupdate"]["state"] = "noupdates"; |
| 59 | hue_bridge_state["lights"]["1"]["swupdate"]["lastinstall"] = nullptr; | 60 | hue_bridge_state["lights"]["1"]["swupdate"]["lastinstall"] = nullptr; |
| @@ -77,6 +78,7 @@ protected: | @@ -77,6 +78,7 @@ protected: | ||
| 77 | hue_bridge_state["lights"]["2"]["state"]["alert"] = "none"; | 78 | hue_bridge_state["lights"]["2"]["state"]["alert"] = "none"; |
| 78 | hue_bridge_state["lights"]["2"]["state"]["colormode"] = "ct"; | 79 | hue_bridge_state["lights"]["2"]["state"]["colormode"] = "ct"; |
| 79 | hue_bridge_state["lights"]["2"]["state"]["reachable"] = true; | 80 | hue_bridge_state["lights"]["2"]["state"]["reachable"] = true; |
| 81 | + hue_bridge_state["lights"]["2"]["state"]["effect"] = "none"; | ||
| 80 | hue_bridge_state["lights"]["2"]["swupdate"] = nlohmann::json::object(); | 82 | hue_bridge_state["lights"]["2"]["swupdate"] = nlohmann::json::object(); |
| 81 | hue_bridge_state["lights"]["2"]["swupdate"]["state"] = "noupdates"; | 83 | hue_bridge_state["lights"]["2"]["swupdate"]["state"] = "noupdates"; |
| 82 | hue_bridge_state["lights"]["2"]["swupdate"]["lastinstall"] = nullptr; | 84 | hue_bridge_state["lights"]["2"]["swupdate"]["lastinstall"] = nullptr; |
| @@ -97,6 +99,7 @@ protected: | @@ -97,6 +99,7 @@ protected: | ||
| 97 | hue_bridge_state["lights"]["3"]["state"]["alert"] = "none"; | 99 | hue_bridge_state["lights"]["3"]["state"]["alert"] = "none"; |
| 98 | hue_bridge_state["lights"]["3"]["state"]["colormode"] = "ct"; | 100 | hue_bridge_state["lights"]["3"]["state"]["colormode"] = "ct"; |
| 99 | hue_bridge_state["lights"]["3"]["state"]["reachable"] = true; | 101 | hue_bridge_state["lights"]["3"]["state"]["reachable"] = true; |
| 102 | + hue_bridge_state["lights"]["3"]["state"]["effect"] = "none"; | ||
| 100 | hue_bridge_state["lights"]["3"]["swupdate"] = nlohmann::json::object(); | 103 | hue_bridge_state["lights"]["3"]["swupdate"] = nlohmann::json::object(); |
| 101 | hue_bridge_state["lights"]["3"]["swupdate"]["state"] = "noupdates"; | 104 | hue_bridge_state["lights"]["3"]["swupdate"]["state"] = "noupdates"; |
| 102 | hue_bridge_state["lights"]["3"]["swupdate"]["lastinstall"] = nullptr; | 105 | hue_bridge_state["lights"]["3"]["swupdate"]["lastinstall"] = nullptr; |