GCC Code Coverage Report


Directory: ./
File: commands/VariableCommands.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 74 79 93.7%
Functions: 3 3 100.0%
Branches: 175 348 50.3%

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 "../Variables.h"
16 #include "../common.h"
17 #include "../log.h"
18 #include "../util/ExpressionProcessor.h"
19
20 #include "VariableCommands.h"
21
22 #include <cstdlib>
23 #include <map>
24 #include <random>
25
26 // std::to_string(double) always pads to 6 decimals ("45.000000"); use a
27 // format that drops trailing zeros so integer-valued results stay readable.
28 48 static std::string DoubleToCleanString(double v) {
29 char buf[64];
30 48 snprintf(buf, sizeof(buf), "%g", v);
31
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
96 return std::string(buf);
32 }
33
34 1888 SetVariableCommand::SetVariableCommand() :
35
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
3776 Command("Set Variable") {
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("name", "string", "Variable Name")
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("The name this value is stored and looked up under - reference it "
38 "elsewhere as %VAR:name% (in any command's text fields) or by picking "
39 "it directly in an If command's Check."));
40
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("type", "string", "Set To")
41
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
5664 .setContentList({ "Value", "Increment", "Random", "Expression" })
42
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setDefaultValue("Value")
43
5/10
✓ 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 7552 times.
✓ Branch 7 taken 1888 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
15104 .setChildren({ { "Value", { "value" } },
44 { "Increment", { "delta" } },
45 { "Random", { "min", "max" } },
46 { "Expression", { "expression" } } })
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("<ul class='mb-0 ps-3 text-start'>"
48 "<li><b>Value</b>: store the text/number below as-is.</li>"
49 "<li><b>Increment</b>: add Amount to the variable's current value (e.g. "
50 "a counter).</li>"
51 "<li><b>Random</b>: pick a whole number between Minimum and Maximum.</li>"
52 "<li><b>Expression</b>: compute a value from a formula, optionally "
53 "referencing other variables by name.</li>"
54 "</ul>"));
55
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("value", "string", "Value")
56
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("The text or number to store."));
57
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("delta", "string", "Amount")
58
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setDefaultValue("1")
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("Added to the variable's current value each time this command runs. Use "
60 "a negative number to count down instead of up."));
61
5/10
✓ 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.
✓ Branch 8 taken 1888 times.
✗ Branch 9 not taken.
13216 args.push_back(CommandArg("min", "string", "Minimum").setDefaultValue("0")
62
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("Lowest whole number that can be picked."));
63
5/10
✓ 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.
✓ Branch 8 taken 1888 times.
✗ Branch 9 not taken.
13216 args.push_back(CommandArg("max", "string", "Maximum").setDefaultValue("100")
64
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("Highest whole number that can be picked."));
65
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("expression", "expression", "Expression")
66
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("A math formula to compute the new value, e.g. =2+3*4. Must start with "
67 "'=', and can reference other variables by name (e.g. =temp*1.8+32 to "
68 "convert temp to Fahrenheit)."));
69
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("persist", "bool", "Persist", true)
70
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 .setDefaultValue("false")
71
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("If checked, this value is saved to disk and reloaded automatically "
72 "the next time FPP starts, so it keeps its value across a restart or "
73 "reboot. If unchecked (the default), the value only lives in memory and "
74 "resets to unset every time FPP restarts."));
75
7/18
✓ 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.
✓ Branch 8 taken 1888 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1888 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1888 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
5664 }
76
77 4656 std::unique_ptr<Command::Result> SetVariableCommand::run(const std::vector<std::string>& args) {
78
3/6
✓ Branch 0 taken 4656 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4656 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4656 times.
4656 if (args.empty() || args[0].empty()) {
79 ✗ return std::make_unique<Command::ErrorResult>("Set Variable requires a Variable Name");
80 }
81 4656 const std::string& name = args[0];
82
4/12
✓ Branch 0 taken 4656 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4656 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4656 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 4656 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
9312 std::string type = args.size() > 1 && !args[1].empty() ? args[1] : "Value";
83
3/10
✓ Branch 0 taken 4656 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4656 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 4656 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
4656 std::string value = args.size() > 2 ? args[2] : "";
84
6/10
✓ Branch 0 taken 3917 times.
✓ Branch 1 taken 739 times.
✓ Branch 2 taken 3917 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 739 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 739 times.
✓ Branch 7 taken 3917 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
5395 std::string deltaStr = args.size() > 3 ? args[3] : "1";
85
6/10
✓ Branch 0 taken 3917 times.
✓ Branch 1 taken 739 times.
✓ Branch 2 taken 3917 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 739 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 739 times.
✓ Branch 7 taken 3917 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
5395 std::string minStr = args.size() > 4 ? args[4] : "0";
86
6/10
✓ Branch 0 taken 3917 times.
✓ Branch 1 taken 739 times.
✓ Branch 2 taken 3917 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 739 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 739 times.
✓ Branch 7 taken 3917 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
5395 std::string maxStr = args.size() > 5 ? args[5] : "100";
87
6/10
✓ Branch 0 taken 3914 times.
✓ Branch 1 taken 742 times.
✓ Branch 2 taken 3914 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 742 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 742 times.
✓ Branch 7 taken 3914 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
5398 std::string expression = args.size() > 6 ? args[6] : "";
88
7/10
✓ Branch 0 taken 1506 times.
✓ Branch 1 taken 3150 times.
✓ Branch 2 taken 1506 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1202 times.
✓ Branch 5 taken 304 times.
✓ Branch 6 taken 1202 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 1202 times.
4656 bool persist = args.size() > 7 && (args[7] == "true" || args[7] == "1");
89
90
3/4
✓ Branch 0 taken 4656 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 4608 times.
4656 if (type == "Increment") {
91 48 double delta = 1.0;
92
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 if (!deltaStr.empty()) {
93 try {
94
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 delta = std::stod(deltaStr);
95 ✗ } catch (...) {
96 ✗ return std::make_unique<Command::ErrorResult>("Invalid amount");
97 ✗ }
98 }
99
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 double newValue = Variables::INSTANCE.incrementVariable(name, delta, persist);
100
2/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
48 return std::make_unique<Command::Result>(DoubleToCleanString(newValue));
101
3/4
✓ Branch 0 taken 4608 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 4602 times.
4608 } else if (type == "Random") {
102 6 int mn = 0, mx = 100;
103 try {
104
6/12
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 5 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 1 times.
8 mn = std::stoi(minStr.empty() ? "0" : minStr);
105
4/12
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 5 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
5 mx = std::stoi(maxStr.empty() ? "100" : maxStr);
106 1 } catch (...) {
107
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 return std::make_unique<Command::ErrorResult>("Invalid min/max");
108
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 }
109
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (mx < mn) {
110 ✗ std::swap(mn, mx);
111 }
112 // The span is computed in 64 bits: across the full int range
113 // mx - mn + 1 overflowed to 0, and rand() % 0 is SIGFPE on x86 and
114 // armhf. A distribution also drops rand()'s modulo bias and its
115 // RAND_MAX ceiling, which made most of a wide range unreachable.
116
5/8
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
5 static thread_local std::mt19937 rng{ std::random_device{}() };
117
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::uniform_int_distribution<long long> dist(mn, mx);
118
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 int result = (int)dist(rng);
119
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 Variables::INSTANCE.setVariable(name, result, persist);
120
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 return std::make_unique<Command::Result>(std::to_string(result));
121
3/4
✓ Branch 0 taken 4602 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1260 times.
✓ Branch 3 taken 3342 times.
4602 } else if (type == "Expression") {
122
1/2
✓ Branch 0 taken 1260 times.
✗ Branch 1 not taken.
1260 ExpressionProcessor proc;
123 // Bind every currently-known Variable by name - User Variables plus
124 // the read-only fpp_/mqtt- ones - so the expression can reference
125 // any of them directly (e.g. "=temp*1.8+32" or "=fpp_volume+10").
126 // ExpressionVariable objects must stay alive and at a stable address
127 // until evaluate() returns, since tinyexpr compiles against their
128 // raw address (ExpressionProcessor.h) -- std::map is node-based, so
129 // inserting more entries never invalidates already-bound pointers.
130 1260 std::map<std::string, ExpressionProcessor::ExpressionVariable> boundVars;
131
3/4
✓ Branch 0 taken 1260 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 37048 times.
✓ Branch 3 taken 1260 times.
38308 for (auto const& varName : Variables::INSTANCE.getAllVariableNames()) {
132
1/2
✓ Branch 0 taken 37048 times.
✗ Branch 1 not taken.
37048 auto inserted = boundVars.try_emplace(varName, varName);
133
3/6
✓ Branch 0 taken 37048 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 37048 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 37048 times.
✗ Branch 5 not taken.
74096 inserted.first->second.setValue(Variables::INSTANCE.getVariable(varName));
134
1/2
✓ Branch 0 taken 37048 times.
✗ Branch 1 not taken.
37048 proc.bindVariable(&inserted.first->second);
135 1260 }
136
3/4
✓ Branch 0 taken 1260 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1253 times.
1260 if (!proc.compile(expression)) {
137
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 LogWarn(VB_COMMAND, "Set Variable: expression failed to compile for \"%s\": %s\n",
138 name.c_str(), TruncateForLog(expression).c_str());
139
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 return std::make_unique<Command::ErrorResult>("Invalid expression");
140 }
141
2/4
✓ Branch 0 taken 1253 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1253 times.
✗ Branch 3 not taken.
1253 std::string result = proc.evaluate("string");
142
1/2
✓ Branch 0 taken 1253 times.
✗ Branch 1 not taken.
1253 Variables::INSTANCE.setVariable(name, result, persist);
143
1/2
✓ Branch 0 taken 1253 times.
✗ Branch 1 not taken.
1253 return std::make_unique<Command::Result>(result);
144 1260 }
145 // "Value" (default/fallback)
146
1/2
✓ Branch 0 taken 3342 times.
✗ Branch 1 not taken.
3342 Variables::INSTANCE.setVariable(name, value, persist);
147
1/2
✓ Branch 0 taken 3342 times.
✗ Branch 1 not taken.
3342 return std::make_unique<Command::Result>("OK");
148 4656 }
149