GCC Code Coverage Report


Directory: ./
File: channeltester/TestPatternBase.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 119 133 89.5%
Functions: 8 9 88.9%
Branches: 94 153 61.4%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include "../common.h"
18 #include "../log.h"
19
20 #include "TestPatternBase.h"
21 #include "channeloutput/ChannelOutputSetup.h"
22 #include "overlays/PixelOverlay.h"
23 #include "overlays/PixelOverlayModel.h"
24
25 /*
26 *
27 */
28 191 TestPatternBase::TestPatternBase() :
29 191 m_testEnabled(0),
30
1/2
✓ Branch 0 taken 191 times.
✗ Branch 1 not taken.
191 m_testPatternName("basePattern"),
31 191 m_minChannels(2),
32 191 m_testData(NULL),
33 191 m_nextCycleTime(0),
34 191 m_cycleMS(0),
35 191 m_channelCount(0),
36 382 m_configChanged(0) {
37
1/2
✓ Branch 0 taken 191 times.
✗ Branch 1 not taken.
191 LogExcess(VB_CHANNELOUT, "TestPatternBase::TestPatternBase()\n");
38
39 // Give room for an extra RGBW Pixel to make coding test patterns easier
40
1/2
✓ Branch 0 taken 191 times.
✗ Branch 1 not taken.
191 m_testData = new char[FPPD_MAX_CHANNELS + 4];
41 191 }
42
43 /*
44 *
45 */
46 242 TestPatternBase::~TestPatternBase() {
47 242 LogExcess(VB_CHANNELOUT, "TestPatternBase::~TestPatternBase()\n");
48
49
1/2
✓ Branch 0 taken 121 times.
✗ Branch 1 not taken.
242 delete[] m_testData;
50 242 }
51
52 /*
53 *
54 */
55 ✗ int TestPatternBase::Init(std::string configStr) {
56 ✗ Json::Value config;
57 ✗ if (!LoadJsonFromString(configStr, config)) {
58 ✗ LogErr(VB_CHANNELOUT,
59 "Error parsing Test Pattern config string: '%s'\n",
60 configStr.c_str());
61 ✗ return 0;
62 }
63
64 ✗ return Init(config);
65 ✗ }
66
67 /*
68 *
69 */
70 194 int TestPatternBase::Init(Json::Value config) {
71
2/4
✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 194 times.
✗ Branch 3 not taken.
194 int cycleMS = JsonInt(config, "cycleMS");
72
2/2
✓ Branch 0 taken 189 times.
✓ Branch 1 taken 5 times.
194 if (m_cycleMS != cycleMS) {
73 189 m_cycleMS = cycleMS;
74 189 m_nextCycleTime = GetMonotonicTimeMicros() + (m_cycleMS * 1000);
75 }
76
77
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 31 times.
194 if (config.isMember("channelSet")) {
78
3/6
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
489 std::string channelSet = JsonString(config, "channelSet");
79
2/2
✓ Branch 0 taken 162 times.
✓ Branch 1 taken 1 times.
163 if (m_channelSetStr != channelSet) {
80
1/2
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
162 m_channelSetStr = channelSet;
81
2/4
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 162 times.
✗ Branch 3 not taken.
162 SetChannelSet(m_channelSetStr);
82 162 m_configChanged = 1;
83 }
84
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
194 } else if (m_channelSetStr != "") {
85 ✗ m_channelSetStr = "";
86 ✗ m_configChanged = 1;
87 ✗ m_channelSet.clear();
88 }
89
90
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 31 times.
194 if (m_configChanged) {
91 163 SetupTest();
92 }
93
94 194 m_testEnabled = 1;
95
96 194 DumpConfig();
97
98 194 return 1;
99 }
100
101 /*
102 *
103 */
104 200 int TestPatternBase::SetChannelSet(std::string channelSetStr) {
105 200 int start = 0;
106 200 int end = 0;
107
108 200 int max = 0;
109
1/2
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
200 auto outputRanges = GetOutputRangesSnapshot();
110
2/2
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 200 times.
400 for (auto& a : *outputRanges) {
111 200 int e = a.first + a.second - 1;
112 200 max = std::max(max, e);
113 }
114
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 181 times.
200 if (max < 8) {
115 19 max = 8;
116 }
117
118 200 m_channelSet.clear();
119 200 m_channelCount = 0;
120 200 m_channelSetRanges = outputRanges;
121
122 // Ranges may be separated by ';' or ','. Only ';' was ever parsed, but the
123 // value the API advertises for this argument - GetOutputRangesAsString(),
124 // published as the ChannelRange contents by /api/fppd/testing/tests/<pattern>
125 // - joins its ranges with ',', so a caller sending that value back got only
126 // the first range: the rest were swallowed by the atoi() below without any
127 // error. A token is looked up as a model name before being split on ',' so
128 // a model whose name contains one still resolves.
129 200 std::vector<std::string> ranges;
130
3/4
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
✓ Branch 3 taken 200 times.
403 for (const auto& tok : split(channelSetStr, ';')) {
131
4/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 202 times.
✓ Branch 2 taken 202 times.
✓ Branch 3 taken 1 times.
204 if (tok.find(',') == std::string::npos ||
132
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 PixelOverlayManager::INSTANCE.getModel(tok) != nullptr) {
133
1/2
✓ Branch 0 taken 202 times.
✗ Branch 1 not taken.
202 ranges.push_back(tok);
134 } else {
135
3/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
4 for (const auto& sub : split(tok, ',')) {
136
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 ranges.push_back(sub);
137 1 }
138 }
139 200 }
140
141
2/2
✓ Branch 0 taken 205 times.
✓ Branch 1 taken 200 times.
405 for (int r = 0; r < ranges.size(); r++) {
142
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 auto model = PixelOverlayManager::INSTANCE.getModelShared(ranges[r]);
143
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 205 times.
205 if (model != nullptr) {
144 ✗ start = model->getStartChannel();
145 ✗ end = start + model->getChannelCount() - 1;
146 } else {
147
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 std::vector<std::string> parts = split(ranges[r], '-');
148
149
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 if (parts.size()) {
150 // atoi() wrapped "3000000000" to a negative start, which the
151 // checks below never caught, so OverlayTestData() copied to
152 // ~1.3 GB before the channel buffer every frame. Parse wide
153 // and clamp into int range; a negative value is rejected below.
154 410 auto channelNum = [](const std::string& v) {
155 410 long long n = strtoll(v.c_str(), nullptr, 10);
156
1/2
✓ Branch 0 taken 410 times.
✗ Branch 1 not taken.
410 return (int)std::clamp<long long>(n, -1, INT_MAX);
157 };
158
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 start = channelNum(parts[0]);
159
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 if (start > 0)
160 205 start--;
161
162
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 if (parts.size() > 1)
163
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 end = channelNum(parts[1]);
164 else
165 ✗ end = start;
166
167
1/2
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
205 if (end > 0)
168 205 end--;
169
170
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 160 times.
205 if (end > max)
171 45 end = max;
172 }
173 205 }
174
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 200 times.
205 if (end < start) {
175 5 end = start;
176 }
177 // Only end was clamped to the outputs, so a range starting past them
178 // ("99999999-99999999") put the copy in OverlayTestData() that far
179 // past the channel buffer: corruption, or a SIGSEGV on the output
180 // thread. Such a range has nothing to test; drop it. The total is
181 // capped at the test buffer too, which overlapping ranges could
182 // otherwise outgrow.
183
3/4
✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 201 times.
205 if (start < 0 || start > max) {
184
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 LogWarn(VB_CHANNELOUT, "Test channel range '%s' is outside the output channels (1-%d), ignoring it\n",
185 ranges[r].c_str(), max + 1);
186 4 continue;
187 }
188 201 int room = (int)FPPD_MAX_CHANNELS - m_channelCount;
189
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 201 times.
201 if (room <= 0) {
190 ✗ break;
191 }
192 201 end = std::min(end, start + room - 1);
193
1/2
✓ Branch 0 taken 201 times.
✗ Branch 1 not taken.
201 m_channelSet.push_back(std::make_pair(start, end));
194 201 m_channelCount += end - start + 1;
195
2/3
✓ Branch 0 taken 201 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
205 }
196
197 200 return 1;
198 200 }
199
200 /*
201 *
202 */
203 170 int TestPatternBase::SetupTest(void) {
204 170 m_nextCycleTime = GetMonotonicTimeMicros() + (m_cycleMS * 1000);
205
206 170 return 1;
207 }
208
209 /*
210 *
211 */
212 30015 int TestPatternBase::OverlayTestData(char* channelData) {
213 30015 int count = 0;
214 30015 int copied = 0;
215
216
2/2
✓ Branch 0 taken 339 times.
✓ Branch 1 taken 29676 times.
30015 if (!m_testEnabled) {
217 // Clear channels being tested
218
2/2
✓ Branch 0 taken 252 times.
✓ Branch 1 taken 339 times.
591 for (int s = 0; s < m_channelSet.size(); s++) {
219 252 count = m_channelSet[s].second - m_channelSet[s].first + 1;
220
221 252 bzero(channelData + m_channelSet[s].first, count);
222 }
223
224 339 return 1;
225 }
226
227 // The channel set was clipped to the outputs as they were when it was
228 // resolved, so resolve it again once they change. Resolved only when
229 // its text changed, a test started before its outputs were loaded (or
230 // while they were being reloaded) stayed clipped to the 1-8 placeholder
231 // there is with no outputs: 1-150 lit channels 1-9 for the whole test.
232
9/12
✓ Branch 0 taken 26115 times.
✓ Branch 1 taken 3561 times.
✓ Branch 2 taken 26115 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 38 times.
✓ Branch 5 taken 26077 times.
✓ Branch 6 taken 26115 times.
✓ Branch 7 taken 3561 times.
✓ Branch 8 taken 38 times.
✓ Branch 9 taken 29638 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
29676 if (!m_channelSetStr.empty() && GetOutputRangesSnapshot() != m_channelSetRanges) {
233
1/2
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
38 auto was = m_channelSet;
234
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
38 SetChannelSet(m_channelSetStr);
235
3/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 31 times.
38 if (m_channelSet != was) {
236
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 SetupTest();
237 }
238 38 }
239
240
2/2
✓ Branch 0 taken 26061 times.
✓ Branch 1 taken 29676 times.
55737 for (int s = 0; s < m_channelSet.size(); s++) {
241 26061 count = m_channelSet[s].second - m_channelSet[s].first + 1;
242
243 26061 memcpy(channelData + m_channelSet[s].first, m_testData + copied,
244 count);
245
246 26061 copied += count;
247 }
248
249 // On the monotonic clock: on the wall clock a step back held the pattern
250 // still for as long as the step. Nothing to cycle when every range was
251 // dropped (the patterns' shifts take a channel off the count, and would
252 // underflow).
253
6/6
✓ Branch 0 taken 26064 times.
✓ Branch 1 taken 3612 times.
✓ Branch 2 taken 6696 times.
✓ Branch 3 taken 19368 times.
✓ Branch 4 taken 6696 times.
✓ Branch 5 taken 22980 times.
29676 if (m_channelCount > 0 && GetMonotonicTimeMicros() >= m_nextCycleTime) {
254 6696 CycleData();
255 6696 m_nextCycleTime = GetMonotonicTimeMicros() + (m_cycleMS * 1000);
256 }
257
258 29676 return 1;
259 }
260
261 /*
262 *
263 */
264 194 void TestPatternBase::DumpConfig(void) {
265 194 LogDebug(VB_CHANNELOUT, "TestPatternBase::DumpConfig()\n");
266 194 LogDebug(VB_CHANNELOUT, " cycleMS : %d\n", m_cycleMS);
267 194 LogDebug(VB_CHANNELOUT, " channelSet : %s\n", m_channelSetStr.c_str());
268 194 LogDebug(VB_CHANNELOUT, " channelCount: %d\n", m_channelCount);
269
270
2/2
✓ Branch 0 taken 164 times.
✓ Branch 1 taken 194 times.
358 for (int s = 0; s < m_channelSet.size(); s++) {
271 164 LogDebug(VB_CHANNELOUT, " : %d-%d\n",
272 m_channelSet[s].first + 1, m_channelSet[s].second + 1);
273 }
274 194 }
275