Coverage Report

Created: 2021-08-28 18:14

D:\git\skunkworks\herald-for-cpp\herald-tests\analysisrunner-tests.cpp
Line
Count
Source (jump to first uncovered line)
1
//  Copyright 2021 Herald project contributors
2
//  SPDX-License-Identifier: Apache-2.0
3
//
4
5
#include "catch.hpp"
6
7
#include "herald/herald.h"
8
9
#include <utility>
10
#include <iostream>
11
12
using namespace herald::analysis::sampling;
13
using namespace herald::datatype;
14
15
template <std::size_t Sz>
16
struct DummyRSSISource {
17
  using value_type = Sample<RSSI>; // allows AnalysisRunner to introspect this class at compile time
18
19
  DummyRSSISource(const std::size_t srcDeviceKey, SampleList<Sample<RSSI>,Sz>&& data)
20
4
    : key(srcDeviceKey), data(std::move(data)), lastAddedAt(0), lastRunAdded(0), hasRan(false) {};
21
4
  ~DummyRSSISource() = default;
22
23
  template <typename RunnerT>
24
14
  void run(std::uint64_t timeTo, RunnerT& runner) {
25
14
    // push through data at default rate
26
14
    lastRunAdded = 0;
27
121
    for (auto& v: data) {
28
121
      // devList.push(v.taken,v.value); // copy data over (It's unusual taking a SampleList and sending to a SampleList)
29
121
      auto sampleTime = v.taken.secondsSinceUnixEpoch();
30
121
      // Only push data that hasn't been pushed yet, otherwise we get an ever increasing sample list
31
121
      if ((!hasRan || 
sampleTime > lastAddedAt100
) &&
(sampleTime <= timeTo)62
) {
32
20
        lastRunAdded++;
33
20
        runner.template newSample<RSSI>(key,v);
34
20
      }
35
121
    }
36
14
    runner.run(Date(timeTo));
37
14
    lastAddedAt = timeTo;
38
14
    hasRan = true;
39
14
  }
40
41
14
  std::uint64_t getLastRunAdded() {
42
14
    return lastRunAdded;
43
14
  }
44
45
private:
46
  std::size_t key;
47
  SampleList<Sample<RSSI>,Sz> data;
48
  std::uint64_t lastAddedAt;
49
  std::uint64_t lastRunAdded;
50
  bool hasRan;
51
};
52
53
struct DummyDistanceDelegate /* : herald::analysis::AnalysisDelegate */ {
54
  using value_type = Distance;
55
56
13
  DummyDistanceDelegate() : lastSampledID(0), distances() {};
57
  DummyDistanceDelegate(const DummyDistanceDelegate&) = delete; // copy ctor deleted
58
6
  DummyDistanceDelegate(DummyDistanceDelegate&& other) noexcept : lastSampledID(other.lastSampledID), distances(std::move(other.distances)) {} // move ctor
59
19
  ~DummyDistanceDelegate() {};
60
61
6
  DummyDistanceDelegate& operator=(DummyDistanceDelegate&& other) noexcept {
62
6
    lastSampledID = other.lastSampledID;
63
6
    std::swap(distances,other.distances);
64
6
    return *this;
65
6
  }
66
67
  // specific override of template
68
8
  void newSample(SampledID sampled, Sample<Distance> sample) {
69
8
    lastSampledID = sampled;
70
8
    distances.push(sample);
71
8
  }
72
73
0
  void reset() {
74
0
    distances.clear();
75
0
    lastSampledID = 0;
76
0
  }
77
78
  // Test only methods
79
6
  SampledID lastSampled() {
80
6
    return lastSampledID;
81
6
  }
82
83
6
  const SampleList<Sample<Distance>,25>& samples() {
84
6
    return distances;
85
6
  }
86
87
private:
88
  SampledID lastSampledID;
89
  SampleList<Sample<Distance>,25> distances;
90
};
91
92
93
1
TEST_CASE("variantset-basic", "[variantset][basic]") {
94
1
  SECTION("variantset-basic") {
95
1
    herald::analysis::VariantSet<int,double> vs;
96
1
    REQUIRE(vs.size() == 2);
97
1
    int intValue = vs.get<int>();
98
1
    REQUIRE(intValue == 0);
99
1
    double dblValue = vs.get<double>();
100
1
    REQUIRE(dblValue == 0.0);
101
1
  }
102
1
}
103
104
1
TEST_CASE("variantset-lists", "[variantset][lists]") {
105
1
  SECTION("variantset-lists") {
106
1
    herald::analysis::VariantSet<SampleList<Sample<int>,15>,SampleList<Sample<double>,15>> vs;
107
1
    REQUIRE(vs.size() == 2);
108
1
    SampleList<Sample<int>,15>& intValue = vs.get<SampleList<Sample<int>,15>>();
109
1
    REQUIRE(intValue.size() == 0);
110
1
    SampleList<Sample<double>,15>& dblValue = vs.get<SampleList<Sample<double>,15>>();
111
1
    REQUIRE(dblValue.size() == 0);
112
1
113
1
    intValue.push(0,12);
114
1
    dblValue.push(10,14.3);
115
1
    dblValue.push(20,15.3);
116
1
117
1
    SampleList<Sample<int>,15>& intValue2 = vs.get<SampleList<Sample<int>,15>>();
118
1
    REQUIRE(intValue2.size() == 1);
119
1
    SampleList<Sample<double>,15>& dblValue2 = vs.get<SampleList<Sample<double>,15>>();
120
1
    REQUIRE(dblValue2.size() == 2);
121
1
122
1
  }
123
1
}
124
125
1
TEST_CASE("variantset-listmanager", "[variantset][listmanager]") {
126
1
  SECTION("variantset-listmanager") {
127
1
    using herald::analysis::ListManager;
128
1
    herald::analysis::VariantSet<ListManager<int,15>,ListManager<double,15>> vs;
129
1
    REQUIRE(vs.size() == 2);
130
1
    ListManager<int,15>& intValue = vs.template get<ListManager<int,15>>();
131
1
    REQUIRE(intValue.size() == 0);
132
1
    ListManager<double,15>& dblValue = vs.template get<ListManager<double,15>>();
133
1
    REQUIRE(dblValue.size() == 0);
134
1
    
135
1
    SampleList<Sample<int>,15>& intList = intValue.list(1234); // list for entity being sampled with SampledId=1234
136
1
    REQUIRE(intList.size() == 0);
137
1
    SampleList<Sample<double>,15>& dblList = dblValue.list(5678); // list for entity being sampled with SampledId=1234
138
1
    REQUIRE(dblList.size() == 0);
139
1
140
1
    intList.push(Sample(0,12));
141
1
    dblList.push(Sample(10,14.3));
142
1
    dblList.push(Sample(20,15.3));
143
1
144
1
    ListManager<int,15>& intValue2 = vs.template get<ListManager<int,15>>();
145
1
    REQUIRE(intValue2.size() == 1);
146
1
    ListManager<double,15>& dblValue2 = vs.template get<ListManager<double,15>>();
147
1
    REQUIRE(dblValue2.size() == 1);
148
1
    
149
1
    SampleList<Sample<int>,15>& intList2 = intValue2.list(1234); // list for entity being sampled with SampledId=1234
150
1
    REQUIRE(intList2.size() == 1);
151
1
    SampleList<Sample<double>,15>& dblList2 = dblValue2.list(5678); // list for entity being sampled with SampledId=1234
152
1
    REQUIRE(dblList2.size() == 2);
153
1
154
1
  }
155
1
}
156
157
/// Null test case with zero data, no failures, correct summary output
158
1
TEST_CASE("analysisrunner-nodata", "[analysisrunner][nodata]") {
159
1
  SECTION("analysisrunner-nodata") {
160
1
    SampleList<Sample<RSSI>,25> srcData;
161
1
    DummyRSSISource src(1234,std::move(srcData));
162
1
163
1
    herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24);
164
1
165
1
    DummyDistanceDelegate myDelegate;
166
1
    herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible
167
1
    herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible
168
1
169
1
    herald::analysis::AnalysisRunner<
170
1
      herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>,
171
1
      herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>,
172
1
      RSSI,Distance
173
1
    > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>)
174
1
175
1
    src.run(140,runner);
176
1
    REQUIRE(src.getLastRunAdded() == 0); // No data, no run
177
1
178
1
    auto& delegateRef = adm.get<DummyDistanceDelegate>();
179
1
    REQUIRE(delegateRef.lastSampled() == 0); // not ran, so 0
180
1
181
1
    auto& samples = delegateRef.samples();
182
1
    REQUIRE(samples.size() == 0); // 0 as there is no source data
183
1
  }
184
1
}
185
186
/// Single data item use case with 1 data item, no failures, correct summary output
187
1
TEST_CASE("analysisrunner-singledataitem", "[analysisrunner][singledataitem]") {
188
1
  SECTION("analysisrunner-singledataitem") {
189
1
    SampleList<Sample<RSSI>,25> srcData;
190
1
    srcData.push(50,-55);
191
1
    DummyRSSISource src(1234,std::move(srcData));
192
1
193
1
    herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24);
194
1
195
1
    DummyDistanceDelegate myDelegate;
196
1
    herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible
197
1
    herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible
198
1
199
1
    herald::analysis::AnalysisRunner<
200
1
      herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>,
201
1
      herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>,
202
1
      RSSI,Distance
203
1
    > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>)
204
1
205
1
    src.run(140,runner);
206
1
    REQUIRE(src.getLastRunAdded() == 1); // Single data item
207
1
208
1
    auto& delegateRef = adm.get<DummyDistanceDelegate>();
209
1
    REQUIRE(delegateRef.lastSampled() == 1234); // ran once, past 50, for SampleID=1234
210
1
211
1
    auto& samples = delegateRef.samples();
212
1
    REQUIRE(samples.size() == 1); // 1 as single data item
213
1
  }
214
1
}
215
216
/// [Who]   As a DCT app developer
217
/// [What]  I want to link my live application data to an analysis runner easily
218
/// [Value] So I don't have to write plumbing code for Herald itself
219
/// 
220
/// [Who]   As a DCT app developer
221
/// [What]  I want to periodically run analysis aggregates automatically
222
/// [Value] So I don't miss any information, and have accurate, regular, samples
223
1
TEST_CASE("analysisrunner-basic", "[analysisrunner][basic]") {
224
1
  SECTION("analysisrunner-basic") {
225
1
    SampleList<Sample<RSSI>,25> srcData;
226
1
    srcData.push(10,-55);
227
1
    srcData.push(20,-55);
228
1
    srcData.push(30,-55);
229
1
    srcData.push(40,-55);
230
1
    srcData.push(50,-55);
231
1
    srcData.push(60,-55);
232
1
    srcData.push(70,-55);
233
1
    srcData.push(80,-55);
234
1
    srcData.push(90,-55);
235
1
    srcData.push(100,-55);
236
1
    DummyRSSISource src(1234,std::move(srcData));
237
1
238
1
    herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24);
239
1
240
1
    DummyDistanceDelegate myDelegate;
241
1
    herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible
242
1
    herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible
243
1
244
1
    herald::analysis::AnalysisRunner<
245
1
      herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>,
246
1
      herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>,
247
1
      RSSI,Distance
248
1
    > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>)
249
1
250
1
    // run at different times and ensure that it only actually runs three times (sample size == 3)
251
1
    src.run(20,runner);
252
1
    REQUIRE(src.getLastRunAdded() == 2); // 10, 20 - THIS IS ZERO BUT MUST BE 2!!!
253
1
    src.run(40,runner); // Runs here, because we have data for 10,20,>>30<<,40 <- next run time based on this 'latest' data time
254
1
    REQUIRE(src.getLastRunAdded() == 2); // 30, 40
255
1
    src.run(60,runner);
256
1
    REQUIRE(src.getLastRunAdded() == 2); // 50, 60
257
1
    src.run(80,runner); // Runs here because we have extra data for 50,60,>>70<<,80 <- next run time based on this 'latest' data time
258
1
    REQUIRE(src.getLastRunAdded() == 2); // 70, 80
259
1
    src.run(95,runner);
260
1
    REQUIRE(src.getLastRunAdded() == 1); // 90
261
1
262
1
    auto& delegateRef = adm.get<DummyDistanceDelegate>();
263
1
    REQUIRE(delegateRef.lastSampled() == 1234);
264
1
265
1
    auto& samples = delegateRef.samples();
266
1
    REQUIRE(samples.size() == 2); // didn't reach 4x30 seconds, so no tenth sample, and didn't run at 60 because previous run was at time 40
267
1
    REQUIRE(samples[0].taken.secondsSinceUnixEpoch() == 40);
268
1
    REQUIRE(samples[0].value != 0.0);
269
1
    REQUIRE(samples[1].taken.secondsSinceUnixEpoch() == 80);
270
1
    REQUIRE(samples[1].value != 0.0);
271
1
272
1
    // Let's see the total memory in use...
273
1
    std::cout << "AnalysisRunner::RAM = " << sizeof(runner) << std::endl;
274
1
  }
275
1
}
276
277
278
/// [Who]   As a DCT app developer
279
/// [What]  I want to link my live application data to an analysis runner easily
280
/// [Value] So I don't have to write plumbing code for Herald itself
281
/// 
282
/// [Who]   As a DCT app developer
283
/// [What]  I want to periodically run analysis aggregates automatically
284
/// [Value] So I don't miss any information, and have accurate, regular, samples
285
1
TEST_CASE("analysisrunner-nonewdata", "[analysisrunner][nonewdata]") {
286
1
  SECTION("analysisrunner-nonewdata") {
287
1
    SampleList<Sample<RSSI>,25> srcData;
288
1
    srcData.push(10,-55);
289
1
    srcData.push(20,-55);
290
1
    srcData.push(30,-55);
291
1
    srcData.push(40,-55);
292
1
    srcData.push(50,-55);
293
1
    srcData.push(60,-55);
294
1
    srcData.push(70,-55);
295
1
    srcData.push(80,-55);
296
1
    srcData.push(90,-55);
297
1
    srcData.push(100,-55);
298
1
    DummyRSSISource src(1234,std::move(srcData));
299
1
300
1
    herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24);
301
1
302
1
    DummyDistanceDelegate myDelegate;
303
1
    herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible
304
1
    herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible
305
1
306
1
    herald::analysis::AnalysisRunner<
307
1
      herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>,
308
1
      herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>,
309
1
      RSSI,Distance
310
1
    > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>)
311
1
312
1
    // run at different times and ensure that it only actually runs three times (sample size == 3)
313
1
    src.run(20,runner);
314
1
    REQUIRE(src.getLastRunAdded() == 2); // 10, 20
315
1
    src.run(40,runner); // Runs here, because we have data for 10,20,>>30<<,40 <- next run time based on this 'latest' data time
316
1
    REQUIRE(src.getLastRunAdded() == 2); // 30, 40
317
1
    src.run(60,runner);
318
1
    REQUIRE(src.getLastRunAdded() == 2); // 50, 60
319
1
    src.run(80,runner); // Runs here because we have extra data for 50,60,>>70<<,80 <- next run time based on this 'latest' data time
320
1
    REQUIRE(src.getLastRunAdded() == 2); // 70, 80
321
1
    src.run(95,runner);
322
1
    REQUIRE(src.getLastRunAdded() == 1); // 90
323
1
324
1
    // Now ensure that running runner past end of data does not cause result to change
325
1
    src.run(115,runner); // Run here because we have past 80 + 30
326
1
    REQUIRE(src.getLastRunAdded() == 1); // 100
327
1
    src.run(150,runner); // Should not run here as there's no new data (even though we're at > 115 + 30)
328
1
    REQUIRE(src.getLastRunAdded() == 0); // No new data
329
1
330
1
    auto& delegateRef = adm.get<DummyDistanceDelegate>();
331
1
    REQUIRE(delegateRef.lastSampled() == 1234);
332
1
333
1
    auto& samples = delegateRef.samples();
334
1
    REQUIRE(samples.size() == 3); // 150 should not result in a run after 145 (min time delay)
335
1
    REQUIRE(samples[0].taken.secondsSinceUnixEpoch() == 40);
336
1
    REQUIRE(samples[0].value != 0.0);
337
1
    REQUIRE(samples[1].taken.secondsSinceUnixEpoch() == 80);
338
1
    REQUIRE(samples[1].value != 0.0);
339
1
    REQUIRE(samples[2].taken.secondsSinceUnixEpoch() == 100); // Last data was at 100, not 115, so it takes that time
340
1
    REQUIRE(samples[2].value != 0.0);
341
1
    // REQUIRE(samples[3].taken.secondsSinceUnixEpoch() == 150);
342
1
    // REQUIRE(samples[3].value != 0.0);
343
1
  }
344
1
}
345
346