ohmu
·
2026-10-15
1#include "common/option.h"
2#include "common/percent.h"
3#include "common/textModifier.h"
4#include "fastfetch.h"
5
6#include <float.h>
7#include <stdio.h>
8#include <stdlib.h>
9
10static FFModuleArgs module;
11
12static void verifyNum(double percent, FFPercentageModuleConfig config, bool parentheses, const char* expected, int lineNo) {
13 FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
14 ffPercentAppendNum(&result, percent, config, parentheses, &module);
15
16 if (!ffStrbufEqualS(&result, expected)) {
17 fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffPercentAppendNum(%f, {%u, %u, %u}, %s): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
18 lineNo, percent, config.green, config.yellow, (unsigned) config.type, parentheses ? "true" : "false", expected, result.chars);
19 exit(1);
20 }
21}
22
23#define VERIFY_NUM(percent, config, parentheses, expected) verifyNum((percent), (config), (parentheses), (expected), __LINE__)
24
25static void verifyBar(double percent, FFPercentageModuleConfig config, const char* expected, int lineNo) {
26 FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
27 ffPercentAppendBar(&result, percent, config, &module);
28
29 if (!ffStrbufEqualS(&result, expected)) {
30 fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffPercentAppendBar(%f, {%u, %u, %u}): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
31 lineNo, percent, config.green, config.yellow, (unsigned) config.type, expected, result.chars);
32 exit(1);
33 }
34}
35
36#define VERIFY_BAR(percent, config, expected) verifyBar((percent), (config), (expected), __LINE__)
37
38int main(void) {
39 // Use the real defaults rather than a hand built config, so a changed default is caught here too
40 ffOptionsInitDisplay(&instance.config.display);
41 FFOptionsDisplay* options = &instance.config.display;
42 ffOptionInitModuleArg(&module, "");
43
44 // Pin the threshold colors: the yellow and red defaults depend on whether the terminal is
45 // detected as a light theme, which is not something a test should depend on.
46 ffStrbufSetS(&options->percentColorGreen, "32");
47 ffStrbufSetS(&options->percentColorYellow, "33");
48 ffStrbufSetS(&options->percentColorRed, "31");
49
50 // The plain number. `pipe` disables the color, which is what the first group of cases checks.
51 {
52 options->pipe = true;
53 const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT | FF_PERCENTAGE_TYPE_NUM_COLOR_BIT };
54
55 VERIFY_NUM(0, config, false, "0%");
56 VERIFY_NUM(50, config, false, "50%");
57 VERIFY_NUM(100, config, false, "100%");
58 VERIFY_NUM(99.9, config, false, "100%"); // the default is zero digits, so this rounds
59
60 // An unset percentage prints as a bare dash, without a percent sign
61 VERIFY_NUM(-DBL_MAX, config, false, "-");
62
63 // Parentheses wrap the whole thing, color included
64 VERIFY_NUM(0, config, true, "(0%)");
65 VERIFY_NUM(-DBL_MAX, config, true, "(-)");
66 }
67
68 // percentNdigits
69 {
70 options->pipe = true;
71 options->percentNdigits = 2;
72 const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
73
74 VERIFY_NUM(0, config, false, "0.00%");
75 VERIFY_NUM(50, config, false, "50.00%");
76 VERIFY_NUM(33.333, config, false, "33.33%");
77 VERIFY_NUM(100, config, false, "100.00%");
78 VERIFY_NUM(-DBL_MAX, config, false, "-");
79
80 options->percentNdigits = 0;
81 }
82
83 // percentWidth pads on the left, and the percent sign is not part of the padded field
84 {
85 options->pipe = true;
86 options->percentWidth = 5;
87 const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
88
89 VERIFY_NUM(0, config, false, " 0%");
90 VERIFY_NUM(50, config, false, " 50%");
91 VERIFY_NUM(100, config, false, " 100%");
92
93 options->percentWidth = 0;
94 }
95
96 // Only `always` adds a space before the percent sign
97 {
98 options->pipe = true;
99 const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
100
101 options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_ALWAYS;
102 VERIFY_NUM(50, config, false, "50 %");
103
104 options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;
105 VERIFY_NUM(50, config, false, "50%");
106
107 options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
108 VERIFY_NUM(50, config, false, "50%");
109 }
110
111 // The color is picked by comparing against the thresholds, and each threshold belongs to the
112 // lower band: exactly `green` is green, exactly `yellow` is yellow.
113 {
114 options->pipe = false;
115 const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_COLOR_BIT };
116
117 VERIFY_NUM(0, config, false, "\e[32m0%\e[m");
118 VERIFY_NUM(50, config, false, "\e[32m50%\e[m");
119 VERIFY_NUM(51, config, false, "\e[33m51%\e[m");
120 VERIFY_NUM(79, config, false, "\e[33m79%\e[m");
121 VERIFY_NUM(80, config, false, "\e[33m80%\e[m");
122 VERIFY_NUM(81, config, false, "\e[31m81%\e[m");
123 VERIFY_NUM(100, config, false, "\e[31m100%\e[m");
124
125 VERIFY_NUM(-DBL_MAX, config, false, "\e[90m-\e[m");
126 VERIFY_NUM(-DBL_MAX, config, true, "(\e[90m-\e[m)");
127 }
128
129 // When `green > yellow` the bands are inverted, so a low value is the alarming one
130 {
131 options->pipe = false;
132 const FFPercentageModuleConfig config = { .green = 80, .yellow = 50, .type = FF_PERCENTAGE_TYPE_NUM_COLOR_BIT };
133
134 VERIFY_NUM(0, config, false, "\e[31m0%\e[m");
135 VERIFY_NUM(49, config, false, "\e[31m49%\e[m");
136 VERIFY_NUM(50, config, false, "\e[33m50%\e[m");
137 VERIFY_NUM(79, config, false, "\e[33m79%\e[m");
138 VERIFY_NUM(80, config, false, "\e[32m80%\e[m");
139 VERIFY_NUM(100, config, false, "\e[32m100%\e[m");
140 }
141
142 // Without the color flag the number is never colored, even when colors are enabled
143 {
144 options->pipe = false;
145 const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
146
147 VERIFY_NUM(0, config, false, "0%");
148 VERIFY_NUM(100, config, false, "100%");
149 }
150
151 // A zero `type` means "use the global default" instead of "no flags"
152 {
153 options->pipe = false;
154 FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = 0 };
155
156 options->percentType = FF_PERCENTAGE_TYPE_NUM_BIT;
157 VERIFY_NUM(100, config, false, "100%");
158
159 options->percentType = FF_PERCENTAGE_TYPE_NUM_BIT | FF_PERCENTAGE_TYPE_NUM_COLOR_BIT;
160 VERIFY_NUM(100, config, false, "\e[31m100%\e[m");
161 }
162
163 // The bar fills `percent` of the width, rounding to the nearest block. Colors are left to the
164 // `pipe` free cases above; the block characters and the borders are what is checked here.
165 {
166 options->pipe = true;
167 const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = 0 };
168
169 options->barWidth = 10;
170 VERIFY_BAR(0, config, "[ ---------- ]");
171 VERIFY_BAR(50, config, "[ ■■■■■----- ]");
172 VERIFY_BAR(100, config, "[ ■■■■■■■■■■ ]");
173
174 // An unset percentage fills the bar with the "total" character, same as zero
175 VERIFY_BAR(-DBL_MAX, config, "[ ---------- ]");
176
177 // The half block is rounded up at exactly 0.5
178 VERIFY_BAR(4, config, "[ ---------- ]");
179 VERIFY_BAR(5, config, "[ ■--------- ]");
180 VERIFY_BAR(14, config, "[ ■--------- ]");
181 VERIFY_BAR(15, config, "[ ■■-------- ]");
182
183 // A value above 100 saturates the bar. That is reachable from real data: the load average is
184 // normalized against the number of cores, so it exceeds 100% as soon as the load does.
185 VERIFY_BAR(101, config, "[ ■■■■■■■■■■ ]");
186 VERIFY_BAR(150, config, "[ ■■■■■■■■■■ ]");
187
188 // Narrowing to `uint8_t` before clamping would wrap around here and silently render 4 blocks
189 VERIFY_BAR(2600, config, "[ ■■■■■■■■■■ ]");
190
191 // A negative value fills nothing
192 VERIFY_BAR(-60, config, "[ ---------- ]");
193
194 options->barWidth = 4;
195 VERIFY_BAR(0, config, "[ ---- ]");
196 VERIFY_BAR(25, config, "[ ■--- ]");
197 VERIFY_BAR(50, config, "[ ■■-- ]");
198 VERIFY_BAR(100, config, "[ ■■■■ ]");
199
200 // The borders are configurable and may be removed entirely
201 ffStrbufSetS(&options->barBorderLeft, "");
202 ffStrbufSetS(&options->barBorderRight, "");
203 VERIFY_BAR(50, config, "■■--");
204
205 ffStrbufSetS(&options->barBorderLeft, "<");
206 ffStrbufSetS(&options->barBorderRight, ">");
207 VERIFY_BAR(50, config, "<■■-->");
208
209 ffStrbufSetS(&options->barBorderLeft, "[ ");
210 ffStrbufSetS(&options->barBorderRight, " ]");
211
212 // The bar characters themselves are configurable
213 ffStrbufSetS(&options->barCharElapsed, "#");
214 ffStrbufSetS(&options->barCharTotal, ".");
215 VERIFY_BAR(50, config, "[ ##.. ]");
216 ffStrbufSetS(&options->barCharElapsed, "■");
217 ffStrbufSetS(&options->barCharTotal, "-");
218
219 options->barWidth = 10;
220 }
221
222 ffOptionDestroyModuleArg(&module);
223 ffOptionsDestroyDisplay(options);
224
225 puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
226 return 0;
227}