GCC Code Coverage Report


Directory: ./
File: commands/IfCommand.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 134 151 88.7%
Functions: 9 9 100.0%
Branches: 214 400 53.5%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2026 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 "Condition.h"
16 #include "../Timers.h"
17 #include "../common.h"
18 #include "../fpp-json.h"
19 #include "../log.h"
20
21 #include "IfCommand.h"
22
23 #include <mutex>
24
25 1888 IfCommand::IfCommand() :
26
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 LocalOnlyCommand("If") {
27 // One condition-tree editor, not two competing ways to say the same
28 // thing - a single Check/Is/Value field pair alongside a separate,
29 // Advanced-only "Rules" box that could add more conditions was
30 // confusing (which one is actually being used?). The "conditionlist"
31 // widget already supports exactly "one condition" through "several
32 // conditions with AND/OR" in one place (fpp.js
33 // RenderConditionListRows only shows the AND/OR toggle once there are
34 // 2+ rows, so the single-condition case still looks simple) - it's
35 // always visible now, not gated behind UI Level.
36
4/8
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
13216 args.push_back(CommandArg("conditions", "conditionlist", "Check")
37
3/6
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
3776 .setHelp("What to test. With more than one condition, choose whether ALL or ANY of them must be true."));
38
4/8
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
13216 args.push_back(CommandArg("thenCommands", "commandlist", "Then Run")
39
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 .setSection("When Check is True")
40
3/6
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
3776 .setHelp("Commands run when the check above is True."));
41
4/8
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
13216 args.push_back(CommandArg("thenMode", "string", "Then Run")
42
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 .setSection("When Check is True")
43
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
5664 .setContentList({ "Sequential", "Parallel" })
44
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setDefaultValue("Sequential")
45
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setToggleStyle()
46
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 .setToggleLabel("Order")
47
3/6
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
3776 .setHelp("Sequential waits for each command to finish before starting the next. "
48 "Parallel fires them all together without waiting."));
49
4/8
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
13216 args.push_back(CommandArg("elseCommands", "commandlist", "Otherwise Run")
50
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setDefaultValue("")
51
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 .setSection("When Check is False")
52
3/6
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
3776 .setHelp("Commands run when the check above is False. Leave empty to do nothing."));
53
4/8
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
13216 args.push_back(CommandArg("elseMode", "string", "Otherwise Run")
54
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 .setSection("When Check is False")
55
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
5664 .setContentList({ "Sequential", "Parallel" })
56
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setDefaultValue("Sequential")
57
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setToggleStyle()
58
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 .setToggleLabel("Order")
59
3/6
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
3776 .setHelp("Sequential waits for each command to finish before starting the next. "
60 "Parallel fires them all together without waiting."));
61 1888 }
62
63 namespace
64 {
65
66 struct CommandListEntry {
67 std::string command;
68 std::vector<std::string> args;
69 };
70
71 1007 std::vector<CommandListEntry> ParseCommandList(const std::string& json) {
72 1007 std::vector<CommandListEntry> result;
73
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1007 times.
1007 if (json.empty()) {
74 ✗ return result;
75 }
76
1/2
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
1007 Json::Value root;
77
2/4
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1007 times.
1007 if (!LoadJsonFromString(json, root)) {
78 ✗ return result; // LoadJsonFromString() already logged the parse error
79 }
80
2/4
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1007 times.
1007 if (!root.isArray()) {
81 ✗ LogWarn(VB_COMMAND, "If: command list JSON was valid but not an array, treating as empty: %s\n",
82 TruncateForLog(json).c_str());
83 ✗ return result;
84 }
85
7/12
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1015 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1015 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2022 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1015 times.
✓ Branch 11 taken 1007 times.
2022 for (auto& entry : root) {
86
3/4
✓ Branch 0 taken 1015 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1012 times.
1015 if (!entry.isObject()) {
87
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 LogWarn(VB_COMMAND, "If: command list entry was not a JSON object, skipping: %s\n",
88 TruncateForLog(SaveJsonToString(entry)).c_str());
89 3 continue;
90 }
91 1012 CommandListEntry e;
92
3/6
✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
1012 e.command = entry.get("command", "").asString();
93
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1012 times.
1012 if (e.command.empty()) {
94 ✗ continue;
95 }
96
6/12
✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1012 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1012 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1012 times.
✗ Branch 11 not taken.
1012 if (entry.isMember("args") && entry["args"].isArray()) {
97
7/12
✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7060 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8072 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 7060 times.
✓ Branch 11 taken 1012 times.
8072 for (auto& a : entry["args"]) {
98
3/6
✓ Branch 0 taken 7060 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7060 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7060 times.
✗ Branch 5 not taken.
7060 e.args.push_back(a.asString());
99 }
100 }
101
1/2
✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
1012 result.push_back(e);
102
1/2
✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
1012 }
103 1007 return result;
104 1007 }
105
106 1012 std::string DescribeEntry(const CommandListEntry& entry) {
107 1012 std::string argsStr;
108
2/2
✓ Branch 0 taken 7060 times.
✓ Branch 1 taken 1012 times.
8072 for (auto& a : entry.args) {
109
2/2
✓ Branch 0 taken 6048 times.
✓ Branch 1 taken 1012 times.
7060 if (!argsStr.empty()) {
110
1/2
✓ Branch 0 taken 6048 times.
✗ Branch 1 not taken.
6048 argsStr += ", ";
111 }
112
4/8
✓ Branch 0 taken 7060 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7060 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7060 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7060 times.
✗ Branch 7 not taken.
7060 argsStr += "\"" + TruncateForLog(a) + "\"";
113 }
114
3/6
✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
2024 return entry.command + "(" + argsStr + ")";
115 1012 }
116
117 // A Sequential Then/Else list that isn't finished yet - the next entry
118 // in "remaining" starts as soon as "inFlight" (the currently-running
119 // entry's Result) reports isDone(). Never blocks the thread that called
120 // If::run(): AdvancePendingChains() (below) is what actually advances
121 // this, driven by a self-re-arming one-shot Timers timer that exists
122 // only while chains are pending (same pattern as
123 // RecurringTasks::armPollTimer()). This replaces the old sleep-loop -
124 // busy-waiting was fine from an HTTP handler thread, but If is also
125 // reached synchronously from Sequence::SendSequenceData() (the
126 // per-frame playback path, via a frame-triggered Command Preset) and
127 // from GPIO edge handlers, where a 1-second stall is a real problem.
128 struct PendingChain {
129 std::vector<CommandListEntry> remaining;
130 std::unique_ptr<Command::Result> inFlight;
131 long long startTimeMS = 0;
132 };
133
134 // Same rationale as RecurringTasks::PENDING_RESULT_MAX_MS - don't let a
135 // single hung command (e.g. a URL fetch with no server-side timeout)
136 // keep a chain alive forever; drop it and move on, same as a normal
137 // Sequential wait giving up after its own bounded poll used to.
138 constexpr long long PENDING_CHAIN_MAX_MS = 60000;
139
140 // How often to re-check an in-flight entry's Result while chains are
141 // pending - roughly the old worst-case advance latency, when the fppd
142 // main loop called a tick every iteration at up to 50ms sleeps.
143 constexpr long long PENDING_POLL_MS = 50;
144
145 std::mutex g_pendingLock;
146 std::vector<PendingChain> g_pendingChains;
147
148 void AdvancePendingChains();
149
150 // One-shot rather than periodic for the same reason as
151 // RecurringTasks::armPollTimer(): a periodic timer stopping itself from
152 // inside its own callback would delete the std::function currently
153 // executing. Every push into g_pendingChains is followed by an arm on
154 // the same thread (RunCommandList runs on arbitrary threads - HTTP
155 // handlers, GPIO edges, the frame path - and Timers::addTimer is
156 // thread-safe), so an advance can never be missed; at worst an arm
157 // races an in-progress advance and the extra fire finds nothing to do.
158 122 void ArmChainPollTimer() {
159
3/6
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 122 times.
✗ Branch 5 not taken.
366 Timers::INSTANCE.addTimer("IfCommand-pendingChains", GetTimeMS() + PENDING_POLL_MS, []() {
160 122 AdvancePendingChains();
161 122 });
162 122 }
163
164 // Starts chain.remaining.front() (removing it from the queue) as the new
165 // inFlight entry, or clears inFlight if the chain is exhausted - the
166 // caller (either RunCommandList kicking a chain off, or tick() advancing
167 // one) is responsible for then deciding whether to keep the chain around.
168 1009 void StartNextEntry(PendingChain& chain) {
169
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1009 times.
1009 if (chain.remaining.empty()) {
170 ✗ chain.inFlight.reset();
171 ✗ return;
172 }
173 1009 CommandListEntry entry = std::move(chain.remaining.front());
174
1/2
✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
1009 chain.remaining.erase(chain.remaining.begin());
175 // Deliberately not gated on WillLog(): a line at Debug or below is
176 // retained in the crash ring even when the configured level discards it
177 // (see CrashLogRingWillCapture), so gating would drop the If tracing
178 // out of crash dumps - which is exactly where it earns its keep.
179
2/4
✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1009 times.
✗ Branch 3 not taken.
1009 LogDebug(VB_COMMAND, "If: running command %s\n", DescribeEntry(entry).c_str());
180
1/2
✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
1009 chain.inFlight = CommandManager::INSTANCE.run(entry.command, entry.args);
181
1/2
✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
1009 chain.startTimeMS = GetMonotonicTimeMS();
182 1009 }
183
184 // Sequential: fires the first entry, then queues the rest as a
185 // PendingChain that IfCommand::tick() advances asynchronously - never
186 // waits on this thread. Parallel: the exact loop shape TriggerPreset
187 // already uses (fire each entry, never wait on isDone() between them) -
188 // unchanged, was never the source of the stall risk.
189 1007 void RunCommandList(std::vector<CommandListEntry> list, bool sequential) {
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1007 times.
1007 if (list.empty()) {
191 1005 return;
192 }
193
3/4
✓ Branch 0 taken 1006 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
1007 LogDebug(VB_COMMAND, "If: running %zu command(s) %s\n", list.size(), sequential ? "sequentially" : "in parallel");
194
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1006 times.
1007 if (!sequential) {
195
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 for (auto& entry : list) {
196 // Ungated for the same reason as StartNextEntry() above.
197
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 LogDebug(VB_COMMAND, "If: running command %s\n", DescribeEntry(entry).c_str());
198
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 CommandManager::INSTANCE.run(entry.command, entry.args);
199 }
200 1 return;
201 }
202 1006 PendingChain chain;
203 1006 chain.remaining = std::move(list);
204
1/2
✓ Branch 0 taken 1006 times.
✗ Branch 1 not taken.
1006 StartNextEntry(chain);
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1006 times.
1006 if (!chain.inFlight) {
206 ✗ return; // shouldn't happen (list wasn't empty), but nothing to track either way
207 }
208
3/4
✓ Branch 0 taken 1006 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1004 times.
✓ Branch 3 taken 2 times.
1006 if (chain.inFlight->isDone()) {
209 // Finished synchronously (most commands do) - just keep going on
210 // this thread rather than parking a chain for tick() to find
211 // already-done on its very next pass.
212
4/8
✓ Branch 0 taken 1004 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1004 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1004 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1004 times.
1004 while (chain.inFlight->isDone() && !chain.remaining.empty()) {
213 ✗ StartNextEntry(chain);
214 }
215
4/8
✓ Branch 0 taken 1004 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1004 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1004 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1004 times.
✗ Branch 7 not taken.
1004 if (chain.inFlight && chain.inFlight->isDone()) {
216 1004 return; // fully drained synchronously
217 }
218 }
219 {
220
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::unique_lock<std::mutex> l(g_pendingLock);
221
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 g_pendingChains.push_back(std::move(chain));
222 2 }
223
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 ArmChainPollTimer();
224
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1004 times.
1006 }
225
226 122 void AdvancePendingChains() {
227 122 std::vector<PendingChain> toAdvance;
228 {
229
1/2
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
122 std::unique_lock<std::mutex> l(g_pendingLock);
230
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (g_pendingChains.empty()) {
231 ✗ return;
232 }
233 122 toAdvance = std::move(g_pendingChains);
234 122 g_pendingChains.clear();
235
1/2
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
122 }
236 122 std::vector<PendingChain> stillPending;
237
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 122 times.
244 for (auto& chain : toAdvance) {
238
1/2
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
122 bool timedOut = (GetMonotonicTimeMS() - chain.startTimeMS) > PENDING_CHAIN_MAX_MS;
239
2/10
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 122 times.
122 if (timedOut && chain.inFlight && !chain.inFlight->isDone()) {
240 ✗ LogWarn(VB_COMMAND, "If: a Sequential command did not finish within %lldms, abandoning the rest of its chain\n",
241 PENDING_CHAIN_MAX_MS);
242 ✗ continue; // drop the whole chain, same as RecurringTasks giving up on a hung task
243 }
244
8/10
✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 120 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 122 times.
125 while (chain.inFlight && chain.inFlight->isDone() && !chain.remaining.empty()) {
245
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 StartNextEntry(chain);
246 }
247
6/8
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 120 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 120 times.
✓ Branch 7 taken 2 times.
122 if (chain.inFlight && !chain.inFlight->isDone()) {
248
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 stillPending.push_back(std::move(chain));
249 }
250 // else: inFlight finished and remaining is empty - chain is done, drop it.
251 }
252
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 2 times.
122 if (!stillPending.empty()) {
253 {
254
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 std::unique_lock<std::mutex> l(g_pendingLock);
255
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 120 times.
240 for (auto& chain : stillPending) {
256
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 g_pendingChains.push_back(std::move(chain));
257 }
258 120 }
259
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 ArmChainPollTimer();
260 }
261
1/2
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
122 }
262
263 } // namespace
264
265 1008 std::unique_ptr<Command::Result> IfCommand::run(const std::vector<std::string>& args) {
266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1008 times.
1008 if (args.size() < 5) {
267 ✗ return std::make_unique<Command::ErrorResult>(
268 ✗ "If requires conditions, thenCommands, thenMode, elseCommands, elseMode");
269 }
270 1008 const std::string& conditions = args[0];
271 1008 const std::string& thenCommandsJson = args[1];
272
1/2
✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
1008 bool thenSequential = args[2] != "Parallel";
273 1008 const std::string& elseCommandsJson = args[3];
274
1/2
✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
1008 bool elseSequential = args[4] != "Parallel";
275
276
2/4
✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1008 times.
✗ Branch 3 not taken.
1008 LogExcess(VB_COMMAND, "If: Check = %s\n", TruncateForLog(conditions, 2000).c_str());
277
278
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1008 times.
1008 if (conditions.empty()) {
279 ✗ return std::make_unique<Command::ErrorResult>("If: no condition configured under Check");
280 }
281
1/2
✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
1008 Json::Value conditionsJson;
282 1008 std::unique_ptr<ConditionNode> condition;
283
2/4
✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1008 times.
✗ Branch 3 not taken.
1008 if (LoadJsonFromString(conditions, conditionsJson)) {
284
2/2
✓ Branch 0 taken 1007 times.
✓ Branch 1 taken 1 times.
1008 condition = ConditionNode::FromJSON(conditionsJson);
285 }
286
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1007 times.
1007 if (!condition) {
287 ✗ return std::make_unique<Command::ErrorResult>("If: could not parse the condition(s) under Check");
288 }
289
290
1/2
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
1007 bool matched = condition->evaluate();
291 // Two tiers, matching the rest of the Command facility: Debug carries the
292 // decision - the causal link between this If and the commands that follow,
293 // which each log themselves at Info from CommandManager::run() - while the
294 // command-list JSON goes to Excessive. That JSON is up to 2000 chars
295 // restating, less legibly, what those next lines already say, and Excessive
296 // is the one level the crash ring does not capture, so it stays out of the
297 // 256 slots the ring has to work with.
298
5/6
✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 977 times.
✓ Branch 3 taken 30 times.
✓ Branch 4 taken 1007 times.
✗ Branch 5 not taken.
1007 LogDebug(VB_COMMAND, "If: overall result = %s -> running %s\n",
299 matched ? "true" : "false", matched ? "Then Run" : "Otherwise Run");
300
6/8
✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 977 times.
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 1007 times.
✗ Branch 7 not taken.
1007 LogExcess(VB_COMMAND, "If: %s command list = %s\n", matched ? "Then Run" : "Otherwise Run",
301 TruncateForLog(matched ? thenCommandsJson : elseCommandsJson, 2000).c_str());
302
3/4
✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
1007 auto list = ParseCommandList(matched ? thenCommandsJson : elseCommandsJson);
303
2/2
✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
1007 bool sequential = matched ? thenSequential : elseSequential;
304
305
1/2
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
1007 if (!list.empty()) {
306
1/2
✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
1007 RunCommandList(std::move(list), sequential);
307 } else {
308 ✗ LogDebug(VB_COMMAND, "If: %s has no commands configured, nothing to run\n",
309 matched ? "Then Run" : "Otherwise Run");
310 }
311
312
3/4
✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
1007 return std::make_unique<Command::Result>(matched ? "true" : "false");
313 1009 }
314