1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_bluetooth_sapphire/internal/host/common/windowed_inspect_numeric_property.h"
16 
17 #include <pw_async/fake_dispatcher_fixture.h>
18 
19 #include "pw_bluetooth_sapphire/internal/host/testing/inspect.h"
20 
21 #ifndef NINSPECT
22 
23 namespace bt {
24 
25 namespace {
26 
27 using namespace ::inspect::testing;
28 
29 template <typename T>
30 class TestProperty {
31  public:
32   using ValueCallback = fit::function<void(const T& value)>;
33   TestProperty() = default;
TestProperty(T value,ValueCallback cb)34   TestProperty(T value, ValueCallback cb)
35       : value_(value), value_cb_(std::move(cb)) {}
36 
Add(const T & value)37   void Add(const T& value) {
38     value_ += value;
39     if (value_cb_) {
40       value_cb_(value_);
41     }
42   }
43 
Subtract(const T & value)44   void Subtract(const T& value) {
45     value_ -= value;
46     if (value_cb_) {
47       value_cb_(value_);
48     }
49   }
50 
51  private:
52   T value_;
53   fit::function<void(const T& value)> value_cb_;
54 };
55 
56 using WindowedProperty = WindowedInspectNumericProperty<TestProperty<int>, int>;
57 using WindowedInspectNumericPropertyTest =
58     pw::async::test::FakeDispatcherFixture;
59 
TEST_F(WindowedInspectNumericPropertyTest,AddTwoValues)60 TEST_F(WindowedInspectNumericPropertyTest, AddTwoValues) {
61   constexpr pw::chrono::SystemClock::duration kExpiryDuration =
62       std::chrono::minutes(3);
63   WindowedProperty windowed_prop(dispatcher(), kExpiryDuration);
64   int value = 0;
65   auto value_cb = [&](auto val) { value = val; };
66   windowed_prop.SetProperty(TestProperty<int>(0, value_cb));
67 
68   windowed_prop.Add(1);
69   EXPECT_EQ(value, 1);
70   RunFor(std::chrono::minutes(1));
71   EXPECT_EQ(value, 1);
72 
73   windowed_prop.Add(2);
74   EXPECT_EQ(value, 3);
75   RunFor(std::chrono::minutes(1));
76   EXPECT_EQ(value, 3);
77 
78   // Let first value expire.
79   RunFor(std::chrono::minutes(1));
80   EXPECT_EQ(value, 2);
81   // Let second value expire.
82   RunFor(std::chrono::minutes(1));
83   EXPECT_EQ(value, 0);
84 
85   // Ensure timer doesn't fire again.
86   RunFor(kExpiryDuration);
87   EXPECT_EQ(value, 0);
88 }
89 
TEST_F(WindowedInspectNumericPropertyTest,AddTwoValuesAtSameTime)90 TEST_F(WindowedInspectNumericPropertyTest, AddTwoValuesAtSameTime) {
91   constexpr pw::chrono::SystemClock::duration kExpiryDuration =
92       std::chrono::minutes(3);
93   WindowedProperty windowed_prop(dispatcher(), kExpiryDuration);
94   int value = 0;
95   auto value_cb = [&](auto val) { value = val; };
96   windowed_prop.SetProperty(TestProperty<int>(0, value_cb));
97 
98   windowed_prop.Add(1);
99   windowed_prop.Add(2);
100   EXPECT_EQ(value, 3);
101   RunFor(std::chrono::minutes(1));
102   EXPECT_EQ(value, 3);
103   RunFor(std::chrono::minutes(2));
104   EXPECT_EQ(value, 0);
105 
106   // Ensure timer doesn't fire again.
107   RunFor(kExpiryDuration);
108   EXPECT_EQ(value, 0);
109 }
110 
TEST_F(WindowedInspectNumericPropertyTest,AddValueThenExpireThenAddValue)111 TEST_F(WindowedInspectNumericPropertyTest, AddValueThenExpireThenAddValue) {
112   constexpr pw::chrono::SystemClock::duration kExpiryDuration =
113       std::chrono::minutes(3);
114   WindowedProperty windowed_prop(dispatcher(), kExpiryDuration);
115   int value = 0;
116   auto value_cb = [&](auto val) { value = val; };
117   windowed_prop.SetProperty(TestProperty<int>(0, value_cb));
118 
119   windowed_prop.Add(1);
120   EXPECT_EQ(value, 1);
121   RunFor(kExpiryDuration);
122   EXPECT_EQ(value, 0);
123 
124   windowed_prop.Add(2);
125   EXPECT_EQ(value, 2);
126   RunFor(kExpiryDuration);
127   EXPECT_EQ(value, 0);
128 
129   // Ensure timer doesn't fire again.
130   RunFor(kExpiryDuration);
131   EXPECT_EQ(value, 0);
132 }
133 
TEST_F(WindowedInspectNumericPropertyTest,AddTwoValuesWithinResolutionIntervalExpiresBothSimultaneously)134 TEST_F(WindowedInspectNumericPropertyTest,
135        AddTwoValuesWithinResolutionIntervalExpiresBothSimultaneously) {
136   constexpr pw::chrono::SystemClock::duration kExpiryDuration =
137       std::chrono::minutes(3);
138   constexpr pw::chrono::SystemClock::duration kResolution =
139       std::chrono::seconds(3);
140   WindowedProperty windowed_prop(dispatcher(), kExpiryDuration, kResolution);
141   int value = 0;
142   auto value_cb = [&](auto val) { value = val; };
143   windowed_prop.SetProperty(TestProperty<int>(0, value_cb));
144 
145   // First two values are within kResolution of each other in time.
146   windowed_prop.Add(1);
147   constexpr pw::chrono::SystemClock::duration kTinyDuration =
148       std::chrono::milliseconds(1);
149   RunFor(kTinyDuration);
150   windowed_prop.Add(1);
151   EXPECT_EQ(value, 2);
152 
153   // Third value is spaced kResolution apart from the first value.
154   RunFor(kResolution - kTinyDuration);
155   windowed_prop.Add(1);
156   EXPECT_EQ(value, 3);
157 
158   // Let first value expire.
159   RunFor(kExpiryDuration - kResolution);
160 
161   // First and second values should have expired because they were merged.
162   EXPECT_EQ(value, 1);
163 
164   // Let third value expire.
165   RunFor(kResolution);
166   EXPECT_EQ(value, 0);
167 }
168 
TEST_F(WindowedInspectNumericPropertyTest,SetPropertyClearsValueAndTimer)169 TEST_F(WindowedInspectNumericPropertyTest, SetPropertyClearsValueAndTimer) {
170   constexpr pw::chrono::SystemClock::duration kExpiryDuration =
171       std::chrono::minutes(3);
172   WindowedProperty windowed_prop(dispatcher(), kExpiryDuration);
173   int value_0 = 0;
174   auto value_cb_0 = [&](auto val) { value_0 = val; };
175   windowed_prop.SetProperty(TestProperty<int>(0, value_cb_0));
176 
177   windowed_prop.Add(1);
178   EXPECT_EQ(value_0, 1);
179   int value_1 = 0;
180   auto value_cb_1 = [&](auto val) { value_1 = val; };
181   windowed_prop.SetProperty(TestProperty<int>(0, value_cb_1));
182   // Ensure timer doesn't fire.
183   RunFor(kExpiryDuration);
184   EXPECT_EQ(value_0, 1);
185   EXPECT_EQ(value_1, 0);
186 
187   // Ensure values can be added to new property.
188   windowed_prop.Add(3);
189   EXPECT_EQ(value_0, 1);
190   EXPECT_EQ(value_1, 3);
191   RunFor(kExpiryDuration);
192   EXPECT_EQ(value_0, 1);
193   EXPECT_EQ(value_1, 0);
194 }
195 
TEST_F(WindowedInspectNumericPropertyTest,AttachInspectRealIntProperty)196 TEST_F(WindowedInspectNumericPropertyTest, AttachInspectRealIntProperty) {
197   ::inspect::Inspector inspector;
198   auto& root = inspector.GetRoot();
199 
200   constexpr pw::chrono::SystemClock::duration kExpiryDuration =
201       std::chrono::minutes(3);
202   WindowedInspectIntProperty windowed_property(dispatcher(), kExpiryDuration);
203   windowed_property.AttachInspect(root, "windowed");
204 
205   auto hierarchy = ::inspect::ReadFromVmo(inspector.DuplicateVmo());
206   ASSERT_TRUE(hierarchy.is_ok());
207   EXPECT_THAT(
208       hierarchy.take_value(),
209       AllOf(NodeMatches(PropertyList(ElementsAre(IntIs("windowed", 0))))));
210 
211   windowed_property.Add(7);
212 
213   hierarchy = ::inspect::ReadFromVmo(inspector.DuplicateVmo());
214   ASSERT_TRUE(hierarchy.is_ok());
215   EXPECT_THAT(
216       hierarchy.take_value(),
217       AllOf(NodeMatches(PropertyList(ElementsAre(IntIs("windowed", 7))))));
218 }
219 
220 }  // namespace
221 
222 }  // namespace bt
223 
224 #endif  // NINSPECT
225