master ohmu/fastfetch-openbsd / tests / percent.c
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}