Tests: adds more UTs

This commit is contained in:
李通洲
2026-09-17 20:11:25 +08:00
parent f8fd708423
commit 6ff7f2772b
10 changed files with 1320 additions and 1 deletions
+64
View File
@@ -2239,6 +2239,62 @@ if (BUILD_TESTS)
PRIVATE libfastfetch
)
add_executable(fastfetch-test-parsing
tests/parsing.c
)
target_link_libraries(fastfetch-test-parsing
PRIVATE libfastfetch
)
add_executable(fastfetch-test-properties
tests/properties.c
)
target_link_libraries(fastfetch-test-properties
PRIVATE libfastfetch
)
add_executable(fastfetch-test-size
tests/size.c
)
target_link_libraries(fastfetch-test-size
PRIVATE libfastfetch
)
add_executable(fastfetch-test-frequency
tests/frequency.c
)
target_link_libraries(fastfetch-test-frequency
PRIVATE libfastfetch
)
add_executable(fastfetch-test-percent
tests/percent.c
)
target_link_libraries(fastfetch-test-percent
PRIVATE libfastfetch
)
add_executable(fastfetch-test-temps
tests/temps.c
)
target_link_libraries(fastfetch-test-temps
PRIVATE libfastfetch
)
add_executable(fastfetch-test-endian
tests/endian.c
)
target_link_libraries(fastfetch-test-endian
PRIVATE libfastfetch
)
add_executable(fastfetch-test-endian-bigendian
tests/endian-bigendian.c
)
target_link_libraries(fastfetch-test-endian-bigendian
PRIVATE libfastfetch
)
enable_testing()
add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf)
add_test(NAME test-list COMMAND fastfetch-test-list)
@@ -2248,6 +2304,14 @@ if (BUILD_TESTS)
add_test(NAME test-strutil COMMAND fastfetch-test-strutil)
add_test(NAME test-networking COMMAND fastfetch-test-networking)
add_test(NAME test-base64 COMMAND fastfetch-test-base64)
add_test(NAME test-parsing COMMAND fastfetch-test-parsing)
add_test(NAME test-properties COMMAND fastfetch-test-properties)
add_test(NAME test-size COMMAND fastfetch-test-size)
add_test(NAME test-frequency COMMAND fastfetch-test-frequency)
add_test(NAME test-percent COMMAND fastfetch-test-percent)
add_test(NAME test-temps COMMAND fastfetch-test-temps)
add_test(NAME test-endian COMMAND fastfetch-test-endian)
add_test(NAME test-endian-bigendian COMMAND fastfetch-test-endian-bigendian)
endif()
##################
+44
View File
@@ -0,0 +1,44 @@
// This translation unit pretends to be compiled on a big endian host, so that the endianness
// detection in `common/endian.h` can be exercised on a little endian machine.
//
// The selection is a preprocessor decision, so it can be pinned with `_Static_assert` instead of by
// running anything -- which matters, because the alternative would be to observe a byte swap at run
// time on hardware that really is little endian, and that cannot work.
//
// The case this guards: GCC does not define `__BIG_ENDIAN__` at all, it only documents
// `__BYTE_ORDER__` and the `__ORDER_*_ENDIAN__` values. A header that tests `#if __BIG_ENDIAN__`
// therefore evaluates it as 0 on s390x and silently picks the little endian branch, byte swapping
// values that were already big endian. `__BIG_ENDIAN__` is undefined here for exactly that reason:
// a header that only looks at its presence, or at its value, fails the assertions below.
//
// System headers are pulled in before the macros are faked so that they are not affected.
#include <stdint.h>
#include <stdio.h>
#include "common/textModifier.h"
// What a big endian compiler reports ...
#undef __BYTE_ORDER__
#define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__
// ... and what GCC does not report on such a machine, even though it is one.
#undef __BIG_ENDIAN__
#include "common/endian.h"
// On a big endian host the byte order of the machine and the byte order of the data agree, so
// reading a big endian value is a no-op ...
_Static_assert(FF_READ_BE((uint16_t) 0x1122) == (uint16_t) 0x1122, "FF_READ_BE must be the identity on a big endian host");
_Static_assert(FF_READ_BE(0x11223344u) == 0x11223344u, "FF_READ_BE must be the identity on a big endian host");
_Static_assert(FF_READ_BE(0x1122334455667788ull) == 0x1122334455667788ull, "FF_READ_BE must be the identity on a big endian host");
// ... and reading a little endian one swaps.
_Static_assert(FF_READ_LE((uint16_t) 0x1122) == (uint16_t) 0x2211, "FF_READ_LE must swap on a big endian host");
_Static_assert(FF_READ_LE(0x11223344u) == 0x44332211u, "FF_READ_LE must swap on a big endian host");
_Static_assert(FF_READ_LE(0x1122334455667788ull) == 0x8877665544332211ull, "FF_READ_LE must swap on a big endian host");
int main(void) {
// Everything this test asserts is checked at compile time, so reaching this line is the result.
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}
+96
View File
@@ -0,0 +1,96 @@
#include "common/endian.h"
#include "common/textModifier.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void verify(bool expression, const char* expressionStr, int lineNo) {
if (expression) {
return;
}
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, expressionStr);
exit(1);
}
#define VERIFY(expression) verify((expression), #expression, __LINE__)
// The macros take a value, so the only way to test them is to lay out the bytes of a known number
// in memory and read them back through the macro. The expectations below do not depend on the
// endianness of the machine running the test:
//
// * copying the little endian byte sequence of 0x11223344 (44 33 22 11) into a `uint32_t` yields
// 0x11223344 on a little endian machine and 0x44332211 on a big endian one. `FF_READ_LE` has to
// turn both of them into 0x11223344, which means it is the identity on one machine and a byte
// swap on the other.
// * `FF_READ_BE` is the mirror image.
//
// On a little endian machine this only pins that the two macros are the identity / swap pair; the
// selection itself (a compiler that does not define `__BIG_ENDIAN__` at all must still be detected
// as big endian from `__BYTE_ORDER__`) can only be exercised by a big endian build, or by a cross
// target such as `clang --target=s390x-linux-gnu`.
static void verify16(void) {
const uint8_t littleEndianBytes[2] = { 0x22, 0x11 };
const uint8_t bigEndianBytes[2] = { 0x11, 0x22 };
uint16_t value;
memcpy(&value, littleEndianBytes, sizeof(value));
VERIFY(FF_READ_LE(value) == (uint16_t) 0x1122);
memcpy(&value, bigEndianBytes, sizeof(value));
VERIFY(FF_READ_BE(value) == (uint16_t) 0x1122);
}
static void verify32(void) {
const uint8_t littleEndianBytes[4] = { 0x44, 0x33, 0x22, 0x11 };
const uint8_t bigEndianBytes[4] = { 0x11, 0x22, 0x33, 0x44 };
uint32_t value;
memcpy(&value, littleEndianBytes, sizeof(value));
VERIFY(FF_READ_LE(value) == 0x11223344u);
memcpy(&value, bigEndianBytes, sizeof(value));
VERIFY(FF_READ_BE(value) == 0x11223344u);
}
static void verify64(void) {
const uint8_t littleEndianBytes[8] = { 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 };
const uint8_t bigEndianBytes[8] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };
uint64_t value;
memcpy(&value, littleEndianBytes, sizeof(value));
VERIFY(FF_READ_LE(value) == 0x1122334455667788ull);
memcpy(&value, bigEndianBytes, sizeof(value));
VERIFY(FF_READ_BE(value) == 0x1122334455667788ull);
}
int main(void) {
verify16();
verify32();
verify64();
// A byte swap has to stay inside the width of the value it was given. A value whose bytes are
// all equal reads the same through either macro, on either kind of machine.
{
uint16_t narrow = 0x0F0Fu;
VERIFY(FF_READ_LE(narrow) == (uint16_t) 0x0F0F);
VERIFY(FF_READ_BE(narrow) == (uint16_t) 0x0F0F);
uint32_t wide = 0x0F0F0F0Fu;
VERIFY(FF_READ_LE(wide) == 0x0F0F0F0Fu);
VERIFY(FF_READ_BE(wide) == 0x0F0F0F0Fu);
uint64_t widest = 0x0F0F0F0F0F0F0F0Full;
VERIFY(FF_READ_LE(widest) == 0x0F0F0F0F0F0F0F0Full);
VERIFY(FF_READ_BE(widest) == 0x0F0F0F0F0F0F0F0Full);
}
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}
+90
View File
@@ -0,0 +1,90 @@
#include "common/frequency.h"
#include "common/textModifier.h"
#include "fastfetch.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static void verify(uint32_t mhz, bool expectedResult, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
const bool returned = ffFreqAppendNum(mhz, &result);
if (returned != expectedResult || !ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffFreqAppendNum(%u): expected %s \"%s\", got %s \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, mhz, expectedResult ? "true" : "false", expected, returned ? "true" : "false", result.chars);
exit(1);
}
}
#define VERIFY_FREQ(mhz, expectedResult, expected) verify((mhz), (expectedResult), (expected), __LINE__)
int main(void) {
// Use the real defaults rather than a hand built config, so a changed default is caught here too
ffOptionsInitDisplay(&instance.config.display);
FFOptionsDisplay* options = &instance.config.display;
// An unknown frequency prints nothing at all
{
VERIFY_FREQ(0, false, "");
}
// With `freqNdigits >= 0` the value is printed in GHz, with that many digits after the point
{
VERIFY_FREQ(1000, true, "1.00 GHz");
VERIFY_FREQ(3600, true, "3.60 GHz");
VERIFY_FREQ(5000, true, "5.00 GHz");
VERIFY_FREQ(100, true, "0.10 GHz");
VERIFY_FREQ(1, true, "0.00 GHz"); // rounds down to zero, but is still reported
VERIFY_FREQ(4294000, true, "4294.00 GHz"); // the whole `uint32_t` range is usable
}
// `freqNdigits = 0` rounds to whole GHz
{
options->freqNdigits = 0;
VERIFY_FREQ(1000, true, "1 GHz");
VERIFY_FREQ(3600, true, "4 GHz"); // rounded, not truncated
VERIFY_FREQ(1400, true, "1 GHz");
VERIFY_FREQ(0, false, "");
options->freqNdigits = 2;
}
// A negative `freqNdigits` selects the integer MHz form instead
{
options->freqNdigits = -1;
VERIFY_FREQ(3600, true, "3600 MHz");
VERIFY_FREQ(1000, true, "1000 MHz");
VERIFY_FREQ(1, true, "1 MHz");
VERIFY_FREQ(0, false, "");
options->freqNdigits = 2;
}
// Only `never` removes the space between the number and the unit
{
options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;
VERIFY_FREQ(3600, true, "3.60GHz");
options->freqNdigits = -1;
VERIFY_FREQ(3600, true, "3600MHz");
options->freqNdigits = 2;
options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_ALWAYS;
VERIFY_FREQ(3600, true, "3.60 GHz");
options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
VERIFY_FREQ(3600, true, "3.60 GHz");
}
// The result is appended to the buffer instead of replacing it
{
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateS("CPU: ");
if (!ffFreqAppendNum(3600, &result) || !ffStrbufEqualS(&result, "CPU: 3.60 GHz")) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffFreqAppendNum did not append: got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, __LINE__, result.chars);
exit(1);
}
}
ffOptionsDestroyDisplay(options);
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}
+190
View File
@@ -0,0 +1,190 @@
#include "common/parsing.h"
#include "common/textModifier.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static void verify(bool expression, const char* expressionStr, int lineNo) {
if (expression) {
return;
}
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, expressionStr);
exit(1);
}
#define VERIFY(expression) verify((expression), #expression, __LINE__)
static void verifyVersionCompare(uint32_t major1, uint32_t minor1, uint32_t patch1, uint32_t major2, uint32_t minor2, uint32_t patch2, int8_t expected, int lineNo) {
const FFVersion first = { major1, minor1, patch1 };
const FFVersion second = { major2, minor2, patch2 };
const int8_t forward = ffVersionCompare(&first, &second);
const int8_t backward = ffVersionCompare(&second, &first);
// Only the sign is meaningful to callers, but the comparison is documented to be antisymmetric,
// so a caller that negates the result to get "greater" must not be surprised.
if (forward != expected || backward != -expected) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffVersionCompare(%u.%u.%u, %u.%u.%u): expected %d/%d, got %d/%d\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, major1, minor1, patch1, major2, minor2, patch2, expected, -expected, forward, backward);
exit(1);
}
}
#define VERIFY_COMPARE(...) verifyVersionCompare(__VA_ARGS__, __LINE__)
static void verifyPretty(uint32_t major, uint32_t minor, uint32_t patch, const char* expected, int lineNo) {
const FFVersion version = { major, minor, patch };
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
ffVersionToPretty(&version, &result);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffVersionToPretty(%u.%u.%u): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, major, minor, patch, expected, result.chars);
exit(1);
}
}
#define VERIFY_PRETTY(...) verifyPretty(__VA_ARGS__, __LINE__)
static void verifySemver(const char* major, const char* minor, const char* patch, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
FF_STRBUF_AUTO_DESTROY majorBuf = ffStrbufCreateS(major);
FF_STRBUF_AUTO_DESTROY minorBuf = ffStrbufCreateS(minor);
FF_STRBUF_AUTO_DESTROY patchBuf = ffStrbufCreateS(patch);
ffParseSemver(&result, &majorBuf, &minorBuf, &patchBuf);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffParseSemver(\"%s\", \"%s\", \"%s\"): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, major, minor, patch, expected, result.chars);
exit(1);
}
}
#define VERIFY_SEMVER(...) verifySemver(__VA_ARGS__, __LINE__)
static void verifyGtk(const char* gtk2, const char* gtk3, const char* gtk4, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
FF_STRBUF_AUTO_DESTROY gtk2Buf = ffStrbufCreateS(gtk2);
FF_STRBUF_AUTO_DESTROY gtk3Buf = ffStrbufCreateS(gtk3);
FF_STRBUF_AUTO_DESTROY gtk4Buf = ffStrbufCreateS(gtk4);
ffParseGTK(&result, &gtk2Buf, &gtk3Buf, &gtk4Buf);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffParseGTK(\"%s\", \"%s\", \"%s\"): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, gtk2, gtk3, gtk4, expected, result.chars);
exit(1);
}
}
#define VERIFY_GTK(...) verifyGtk(__VA_ARGS__, __LINE__)
int main(void) {
// ffVersionCompare: the major field decides first, then minor, then patch. Equal versions
// compare equal even when they come from different sources.
{
VERIFY_COMPARE(0, 0, 0, 0, 0, 0, 0);
VERIFY_COMPARE(1, 2, 3, 1, 2, 3, 0);
VERIFY_COMPARE(UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, 0);
VERIFY_COMPARE(2, 0, 0, 1, 99, 99, 1);
VERIFY_COMPARE(1, 99, 99, 2, 0, 0, -1);
VERIFY_COMPARE(1, 2, 0, 1, 1, 99, 1);
VERIFY_COMPARE(1, 1, 99, 1, 2, 0, -1);
VERIFY_COMPARE(1, 2, 3, 1, 2, 2, 1);
VERIFY_COMPARE(1, 2, 2, 1, 2, 3, -1);
// A larger patch must not be able to outvote a smaller minor
VERIFY_COMPARE(1, 2, 0, 1, 1, UINT32_MAX, 1);
VERIFY_COMPARE(1, 1, UINT32_MAX, 1, 2, 0, -1);
// ... nor a larger minor a smaller major
VERIFY_COMPARE(2, 0, 0, 1, UINT32_MAX, UINT32_MAX, 1);
VERIFY_COMPARE(1, UINT32_MAX, UINT32_MAX, 2, 0, 0, -1);
}
// ffVersionToPretty: a zero version prints as nothing at all, and a field is only printed when
// it or a later field is non-zero.
{
VERIFY_PRETTY(0, 0, 0, "");
VERIFY_PRETTY(1, 0, 0, "1");
VERIFY_PRETTY(0, 1, 0, "0.1");
VERIFY_PRETTY(0, 0, 1, "0.0.1");
VERIFY_PRETTY(1, 2, 0, "1.2");
VERIFY_PRETTY(1, 0, 3, "1.0.3");
VERIFY_PRETTY(1, 2, 3, "1.2.3");
VERIFY_PRETTY(10, 20, 30, "10.20.30");
VERIFY_PRETTY(UINT32_MAX, 0, 0, "4294967295");
VERIFY_PRETTY(0, 0, UINT32_MAX, "0.0.4294967295");
}
// ffParseSemver: joins whichever parts were detected. A missing major is reported as 1, which is
// how "2.3" from a source that only reports the last two components stays unambiguous.
{
VERIFY_SEMVER("1", "2", "3", "1.2.3");
VERIFY_SEMVER("1", "2", "", "1.2");
VERIFY_SEMVER("1", "", "", "1");
VERIFY_SEMVER("1", "", "3", "1.0.3");
VERIFY_SEMVER("", "2", "3", "1.2.3");
VERIFY_SEMVER("", "2", "", "1.2");
VERIFY_SEMVER("", "", "3", "1.0.3");
VERIFY_SEMVER("", "", "", "");
// A zero part is a value, not a missing part
VERIFY_SEMVER("0", "0", "0", "0.0.0");
VERIFY_SEMVER("0", "", "", "0");
}
// ffParseSemver appends instead of replacing, so a caller can prefix its own text
{
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateS("v");
FF_STRBUF_AUTO_DESTROY majorBuf = ffStrbufCreateS("1");
FF_STRBUF_AUTO_DESTROY minorBuf = ffStrbufCreateS("2");
FF_STRBUF_AUTO_DESTROY patchBuf = ffStrbufCreateS("3");
ffParseSemver(&result, &majorBuf, &minorBuf, &patchBuf);
VERIFY(ffStrbufEqualS(&result, "v1.2.3"));
}
// ffParseGTK: the three versions are folded into one line, and every combination of present and
// equal versions has its own suffix.
{
// All three present
VERIFY_GTK("1.0", "1.0", "1.0", "1.0 [GTK2/3/4]");
VERIFY_GTK("2.0", "2.0", "3.0", "2.0 [GTK2/3], 3.0 [GTK4]");
VERIFY_GTK("2.0", "3.0", "3.0", "2.0 [GTK2], 3.0 [GTK3/4]");
VERIFY_GTK("2.0", "3.0", "4.0", "2.0 [GTK2], 3.0 [GTK3], 4.0 [GTK4]");
// GTK2 and GTK4 equal while GTK3 differs is not special cased: it falls back to the plain
// three-way listing, so the same version is printed twice with different suffixes.
VERIFY_GTK("2.0", "3.0", "2.0", "2.0 [GTK2], 3.0 [GTK3], 2.0 [GTK4]");
// The comparison is case insensitive, but the value printed is the one from the buffer that
// the branch selected -- here the third one, not the first.
VERIFY_GTK("ABC", "abc", "aBc", "aBc [GTK2/3/4]");
VERIFY_GTK("2.0", "2.0", "3.0", "2.0 [GTK2/3], 3.0 [GTK4]");
// Two present
VERIFY_GTK("1.0", "1.0", "", "1.0 [GTK2/3]");
VERIFY_GTK("2.0", "3.0", "", "2.0 [GTK2], 3.0 [GTK3]");
VERIFY_GTK("1.0", "", "1.0", "1.0 [GTK2/4]");
VERIFY_GTK("2.0", "", "4.0", "2.0 [GTK2], 4.0 [GTK4]");
VERIFY_GTK("", "1.0", "1.0", "1.0 [GTK3/4]");
VERIFY_GTK("", "3.0", "4.0", "3.0 [GTK3], 4.0 [GTK4]");
// One present
VERIFY_GTK("2.0", "", "", "2.0 [GTK2]");
VERIFY_GTK("", "3.0", "", "3.0 [GTK3]");
VERIFY_GTK("", "", "4.0", "4.0 [GTK4]");
// None present
VERIFY_GTK("", "", "", "");
}
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}
+216
View File
@@ -0,0 +1,216 @@
#include "common/option.h"
#include "common/percent.h"
#include "common/textModifier.h"
#include "fastfetch.h"
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
static FFModuleArgs module;
static void verifyNum(double percent, FFPercentageModuleConfig config, bool parentheses, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
ffPercentAppendNum(&result, percent, config, parentheses, &module);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffPercentAppendNum(%f, {%u, %u, %u}, %s): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, percent, config.green, config.yellow, (unsigned) config.type, parentheses ? "true" : "false", expected, result.chars);
exit(1);
}
}
#define VERIFY_NUM(percent, config, parentheses, expected) verifyNum((percent), (config), (parentheses), (expected), __LINE__)
static void verifyBar(double percent, FFPercentageModuleConfig config, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
ffPercentAppendBar(&result, percent, config, &module);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffPercentAppendBar(%f, {%u, %u, %u}): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, percent, config.green, config.yellow, (unsigned) config.type, expected, result.chars);
exit(1);
}
}
#define VERIFY_BAR(percent, config, expected) verifyBar((percent), (config), (expected), __LINE__)
int main(void) {
// Use the real defaults rather than a hand built config, so a changed default is caught here too
ffOptionsInitDisplay(&instance.config.display);
FFOptionsDisplay* options = &instance.config.display;
ffOptionInitModuleArg(&module, "");
// Pin the threshold colors: the yellow and red defaults depend on whether the terminal is
// detected as a light theme, which is not something a test should depend on.
ffStrbufSetS(&options->percentColorGreen, "32");
ffStrbufSetS(&options->percentColorYellow, "33");
ffStrbufSetS(&options->percentColorRed, "31");
// The plain number. `pipe` disables the color, which is what the first group of cases checks.
{
options->pipe = true;
const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT | FF_PERCENTAGE_TYPE_NUM_COLOR_BIT };
VERIFY_NUM(0, config, false, "0%");
VERIFY_NUM(50, config, false, "50%");
VERIFY_NUM(100, config, false, "100%");
VERIFY_NUM(99.9, config, false, "100%"); // the default is zero digits, so this rounds
// An unset percentage prints as a bare dash, without a percent sign
VERIFY_NUM(-DBL_MAX, config, false, "-");
// Parentheses wrap the whole thing, color included
VERIFY_NUM(0, config, true, "(0%)");
VERIFY_NUM(-DBL_MAX, config, true, "(-)");
}
// percentNdigits
{
options->pipe = true;
options->percentNdigits = 2;
const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
VERIFY_NUM(0, config, false, "0.00%");
VERIFY_NUM(50, config, false, "50.00%");
VERIFY_NUM(33.333, config, false, "33.33%");
VERIFY_NUM(100, config, false, "100.00%");
VERIFY_NUM(-DBL_MAX, config, false, "-");
options->percentNdigits = 0;
}
// percentWidth pads on the left, and the percent sign is not part of the padded field
{
options->pipe = true;
options->percentWidth = 5;
const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
VERIFY_NUM(0, config, false, " 0%");
VERIFY_NUM(50, config, false, " 50%");
VERIFY_NUM(100, config, false, " 100%");
options->percentWidth = 0;
}
// Only `always` adds a space before the percent sign
{
options->pipe = true;
const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_ALWAYS;
VERIFY_NUM(50, config, false, "50 %");
options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;
VERIFY_NUM(50, config, false, "50%");
options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
VERIFY_NUM(50, config, false, "50%");
}
// The color is picked by comparing against the thresholds, and each threshold belongs to the
// lower band: exactly `green` is green, exactly `yellow` is yellow.
{
options->pipe = false;
const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_COLOR_BIT };
VERIFY_NUM(0, config, false, "\e[32m0%\e[m");
VERIFY_NUM(50, config, false, "\e[32m50%\e[m");
VERIFY_NUM(51, config, false, "\e[33m51%\e[m");
VERIFY_NUM(79, config, false, "\e[33m79%\e[m");
VERIFY_NUM(80, config, false, "\e[33m80%\e[m");
VERIFY_NUM(81, config, false, "\e[31m81%\e[m");
VERIFY_NUM(100, config, false, "\e[31m100%\e[m");
VERIFY_NUM(-DBL_MAX, config, false, "\e[90m-\e[m");
VERIFY_NUM(-DBL_MAX, config, true, "(\e[90m-\e[m)");
}
// When `green > yellow` the bands are inverted, so a low value is the alarming one
{
options->pipe = false;
const FFPercentageModuleConfig config = { .green = 80, .yellow = 50, .type = FF_PERCENTAGE_TYPE_NUM_COLOR_BIT };
VERIFY_NUM(0, config, false, "\e[31m0%\e[m");
VERIFY_NUM(49, config, false, "\e[31m49%\e[m");
VERIFY_NUM(50, config, false, "\e[33m50%\e[m");
VERIFY_NUM(79, config, false, "\e[33m79%\e[m");
VERIFY_NUM(80, config, false, "\e[32m80%\e[m");
VERIFY_NUM(100, config, false, "\e[32m100%\e[m");
}
// Without the color flag the number is never colored, even when colors are enabled
{
options->pipe = false;
const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = FF_PERCENTAGE_TYPE_NUM_BIT };
VERIFY_NUM(0, config, false, "0%");
VERIFY_NUM(100, config, false, "100%");
}
// A zero `type` means "use the global default" instead of "no flags"
{
options->pipe = false;
FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = 0 };
options->percentType = FF_PERCENTAGE_TYPE_NUM_BIT;
VERIFY_NUM(100, config, false, "100%");
options->percentType = FF_PERCENTAGE_TYPE_NUM_BIT | FF_PERCENTAGE_TYPE_NUM_COLOR_BIT;
VERIFY_NUM(100, config, false, "\e[31m100%\e[m");
}
// The bar fills `percent` of the width, rounding to the nearest block. Colors are left to the
// `pipe` free cases above; the block characters and the borders are what is checked here.
{
options->pipe = true;
const FFPercentageModuleConfig config = { .green = 50, .yellow = 80, .type = 0 };
options->barWidth = 10;
VERIFY_BAR(0, config, "[ ---------- ]");
VERIFY_BAR(50, config, "[ ■■■■■----- ]");
VERIFY_BAR(100, config, "[ ■■■■■■■■■■ ]");
// An unset percentage fills the bar with the "total" character, same as zero
VERIFY_BAR(-DBL_MAX, config, "[ ---------- ]");
// The half block is rounded up at exactly 0.5
VERIFY_BAR(4, config, "[ ---------- ]");
VERIFY_BAR(5, config, "[ ■--------- ]");
VERIFY_BAR(14, config, "[ ■--------- ]");
VERIFY_BAR(15, config, "[ ■■-------- ]");
options->barWidth = 4;
VERIFY_BAR(0, config, "[ ---- ]");
VERIFY_BAR(25, config, "[ ■--- ]");
VERIFY_BAR(50, config, "[ ■■-- ]");
VERIFY_BAR(100, config, "[ ■■■■ ]");
// The borders are configurable and may be removed entirely
ffStrbufSetS(&options->barBorderLeft, "");
ffStrbufSetS(&options->barBorderRight, "");
VERIFY_BAR(50, config, "■■--");
ffStrbufSetS(&options->barBorderLeft, "<");
ffStrbufSetS(&options->barBorderRight, ">");
VERIFY_BAR(50, config, "<■■-->");
ffStrbufSetS(&options->barBorderLeft, "[ ");
ffStrbufSetS(&options->barBorderRight, " ]");
// The bar characters themselves are configurable
ffStrbufSetS(&options->barCharElapsed, "#");
ffStrbufSetS(&options->barCharTotal, ".");
VERIFY_BAR(50, config, "[ ##.. ]");
ffStrbufSetS(&options->barCharElapsed, "■");
ffStrbufSetS(&options->barCharTotal, "-");
options->barWidth = 10;
}
ffOptionDestroyModuleArg(&module);
ffOptionsDestroyDisplay(options);
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}
+138
View File
@@ -0,0 +1,138 @@
#include "common/properties.h"
#include "common/textModifier.h"
#include <stdio.h>
#include <stdlib.h>
static void verify(bool expression, const char* expressionStr, int lineNo) {
if (expression) {
return;
}
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, expressionStr);
exit(1);
}
#define VERIFY(expression) verify((expression), #expression, __LINE__)
static void verifyLine(const char* line, const char* start, bool expectedResult, const char* expectedValue, int lineNo) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
const char* cursor = line;
const bool result = ffParsePropLinePointer(&cursor, start, &buffer);
if (result != expectedResult || (result && !ffStrbufEqualS(&buffer, expectedValue))) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffParsePropLine(\"%s\", \"%s\"): expected %s \"%s\", got %s \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, line, start, expectedResult ? "true" : "false", expectedResult ? expectedValue : "",
result ? "true" : "false", result ? buffer.chars : "");
exit(1);
}
}
#define VERIFY_LINE(...) verifyLine(__VA_ARGS__, __LINE__)
static void verifyLines(const char* lines, const char* start, bool expectedResult, const char* expectedValue, int lineNo) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
const bool result = ffParsePropLines(lines, start, &buffer);
if (result != expectedResult || (result && !ffStrbufEqualS(&buffer, expectedValue))) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffParsePropLines(\"%s\", \"%s\"): expected %s \"%s\", got %s \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, lines, start, expectedResult ? "true" : "false", expectedResult ? expectedValue : "",
result ? "true" : "false", result ? buffer.chars : "");
exit(1);
}
}
#define VERIFY_LINES(...) verifyLines(__VA_ARGS__, __LINE__)
int main(void) {
// The plain case: `key: value`, with the value running to the end of the line.
{
VERIFY_LINE("Name: foo", "Name:", true, "foo");
VERIFY_LINE("Name: foo\n", "Name:", true, "foo");
VERIFY_LINE("Name: foo\nBar: baz", "Name:", true, "foo"); // stops at the newline
VERIFY_LINE("Name:foo", "Name:", true, "foo");
VERIFY_LINE("Name: ", "Name:", true, ""); // found, but the value is empty
VERIFY_LINE("Name:", "Name:", true, "");
VERIFY_LINE("Name: a:b", "Name:", true, "a:b"); // only the first colon is the separator
VERIFY_LINE("Name: foo bar", "Name:", true, "foo bar"); // spaces inside the value are kept
}
// Any amount of whitespace in the format matches any amount of whitespace in the line, including
// none -- the two sides are independent.
{
VERIFY_LINE("Name: foo", "Name:", true, "foo");
VERIFY_LINE("Name:foo", "Name: ", true, "foo");
VERIFY_LINE("Name: foo", "Name: ", true, "foo");
VERIFY_LINE("Name:\tfoo", "Name: ", true, "foo");
VERIFY_LINE("Name: foo", "Name:\t", true, "foo");
VERIFY_LINE(" \t Name: foo", "Name:", true, "foo"); // leading whitespace of the line is skipped
}
// A mismatch anywhere in the key makes the whole line fail, and the key must match in full.
{
VERIFY_LINE("Name: foo", "Value:", false, "");
VERIFY_LINE("name: foo", "Name:", true, "foo"); // the key is compared case insensitively
VERIFY_LINE("NAME: foo", "name:", true, "foo");
VERIFY_LINE("nAmE: foo", "NAME:", true, "foo");
VERIFY_LINE("NameX: foo", "Name:", false, "");
VERIFY_LINE("Name :foo", "Name:", false, "");
VERIFY_LINE("Nam", "Name:", false, ""); // the line ends before the key does
VERIFY_LINE("", "Name:", false, "");
VERIFY_LINE("\n", "Name:", false, "");
VERIFY_LINE("Name", "Name:", false, "");
}
// Trailing spaces are trimmed, but nothing else is: a trailing tab survives.
{
VERIFY_LINE("Name: foo ", "Name:", true, "foo");
VERIFY_LINE("Name: foo\t", "Name:", true, "foo\t");
VERIFY_LINE("Name: foo \n", "Name:", true, "foo");
}
// A quoted value ends at its closing quote, so trailing text is not part of the value.
{
VERIFY_LINE("Name: \"foo bar\"", "Name:", true, "foo bar");
VERIFY_LINE("Name: 'foo bar'", "Name:", true, "foo bar");
VERIFY_LINE("Name: \"foo\" trailing", "Name:", true, "foo");
VERIFY_LINE("Name: \"\"", "Name:", true, "");
VERIFY_LINE("Name: \"foo", "Name:", true, "foo"); // unterminated quote: the line end closes it
// The trim still applies, and it only removes the right hand side
VERIFY_LINE("Name: \" foo \"", "Name:", true, " foo");
}
// A key ending in `>` switches the parser to XML mode, where the value ends at `<` instead of at
// the end of the line.
{
VERIFY_LINE("<name>foo</name>", "<name>", true, "foo");
VERIFY_LINE("<name></name>", "<name>", true, "");
VERIFY_LINE("<name>foo", "<name>", true, "foo"); // no closing tag
VERIFY_LINE("<name>foo bar</name>", "<name>", true, "foo bar");
VERIFY_LINE("<name> foo</name>", "<name>", true, "foo");
VERIFY_LINE("<name>foo\n", "<name>", true, "foo");
}
// ffParsePropLines walks the lines until the key is found, and the first match wins -- including
// when that match has an empty value.
{
VERIFY_LINES("A: 1\nName: foo\nB: 2", "Name:", true, "foo");
VERIFY_LINES("A: 1\nName: foo", "Name:", true, "foo");
VERIFY_LINES("Name: foo", "Name:", true, "foo");
VERIFY_LINES("\n\nName: foo", "Name:", true, "foo");
VERIFY_LINES("Name: foo\nName: bar", "Name:", true, "foo");
VERIFY_LINES("Name:\nName: bar", "Name:", true, "");
VERIFY_LINES("A: 1\nB: 2", "Name:", false, "");
VERIFY_LINES("A: 1\nB: 2\n", "Name:", false, "");
VERIFY_LINES("", "Name:", false, "");
}
// The value is appended to the buffer, so a caller can collect several keys into one string.
{
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateS("pre");
VERIFY(ffParsePropLines("Name: foo", "Name:", &buffer));
VERIFY(ffStrbufEqualS(&buffer, "prefoo"));
}
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}
+142
View File
@@ -0,0 +1,142 @@
#include "common/size.h"
#include "common/textModifier.h"
#include "fastfetch.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static void verify(uint64_t bytes, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
ffSizeAppendNum(bytes, &result);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffSizeAppendNum(%llu): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, (unsigned long long) bytes, expected, result.chars);
exit(1);
}
}
#define VERIFY_SIZE(bytes, expected) verify((bytes), (expected), __LINE__)
int main(void) {
// Use the real defaults rather than a hand built config, so a changed default is caught here too
ffOptionsInitDisplay(&instance.config.display);
FFOptionsDisplay* options = &instance.config.display;
// IEC (the default): 1024 based, with the `KiB` style suffixes
{
VERIFY_SIZE(0, "0 B");
VERIFY_SIZE(1, "1 B");
VERIFY_SIZE(999, "999 B");
VERIFY_SIZE(1023, "1023 B"); // one byte short of the first prefix
VERIFY_SIZE(1024, "1.00 KiB");
VERIFY_SIZE(1025, "1.00 KiB"); // below the rounding precision
VERIFY_SIZE(1536, "1.50 KiB");
VERIFY_SIZE(1024ULL * 1024, "1.00 MiB");
VERIFY_SIZE(1024ULL * 1024 * 1024, "1.00 GiB");
VERIFY_SIZE(1024ULL * 1024 * 1024 * 1024, "1.00 TiB");
VERIFY_SIZE(1024ULL * 1024 * 1024 * 1024 * 1024, "1.00 PiB");
VERIFY_SIZE(1024ULL * 1024 * 1024 * 1024 * 1024 * 1024, "1.00 EiB");
// The largest 64 bit value is 16 EiB; `ZiB` and `YiB` are listed but can never be reached
// through an IEC conversion of a `uint64_t`.
VERIFY_SIZE(UINT64_MAX, "16.00 EiB");
}
// SI: 1000 based, with the `kB` style suffixes
{
options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_SI;
VERIFY_SIZE(0, "0 B");
VERIFY_SIZE(999, "999 B");
VERIFY_SIZE(1000, "1.00 kB");
VERIFY_SIZE(1500, "1.50 kB");
VERIFY_SIZE(1000000, "1.00 MB");
VERIFY_SIZE(1000000000ULL, "1.00 GB");
VERIFY_SIZE(1000000000000ULL, "1.00 TB");
VERIFY_SIZE(1000000000000000ULL, "1.00 PB");
VERIFY_SIZE(1000000000000000000ULL, "1.00 EB");
VERIFY_SIZE(UINT64_MAX, "18.45 EB");
}
// JEDEC: 1024 based, but with the `KB` style suffixes. The suffix list stops at `TB`, so the
// loop has to stop there as well instead of reading past the end.
{
options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_JEDEC;
VERIFY_SIZE(1023, "1023 B");
VERIFY_SIZE(1024, "1.00 KB");
VERIFY_SIZE(1024ULL * 1024, "1.00 MB");
VERIFY_SIZE(1024ULL * 1024 * 1024, "1.00 GB");
VERIFY_SIZE(1024ULL * 1024 * 1024 * 1024, "1.00 TB");
VERIFY_SIZE(1024ULL * 1024 * 1024 * 1024 * 1024, "1024.00 TB"); // no `PB` in JEDEC
VERIFY_SIZE(UINT64_MAX, "16777216.00 TB");
}
options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_IEC;
// sizeNdigits: the number of digits after the decimal point. It applies only once a prefix is
// used -- a plain byte count is always printed as an integer.
{
options->sizeNdigits = 0;
VERIFY_SIZE(1024, "1 KiB");
VERIFY_SIZE(1536, "2 KiB"); // rounded, not truncated
VERIFY_SIZE(512, "512 B");
VERIFY_SIZE(1023, "1023 B");
options->sizeNdigits = 4;
VERIFY_SIZE(1024, "1.0000 KiB");
VERIFY_SIZE(1536, "1.5000 KiB");
VERIFY_SIZE(512, "512 B"); // still an integer
VERIFY_SIZE(0, "0 B");
options->sizeNdigits = 2;
}
// sizeMaxPrefix: the index of the largest suffix that may be used. `0` disables the conversion
// entirely.
{
options->sizeMaxPrefix = 0;
VERIFY_SIZE(0, "0 B");
VERIFY_SIZE(1024, "1024 B");
VERIFY_SIZE(UINT64_MAX, "18446744073709551615 B");
options->sizeMaxPrefix = 1;
VERIFY_SIZE(1024, "1.00 KiB");
VERIFY_SIZE(1024ULL * 1024, "1024.00 KiB");
VERIFY_SIZE(1024ULL * 1024 * 1024, "1048576.00 KiB");
options->sizeMaxPrefix = 8;
}
// sizeSpaceBeforeUnit: only `never` removes the space, `default` and `always` both keep it
{
options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;
VERIFY_SIZE(0, "0B");
VERIFY_SIZE(1024, "1.00KiB");
VERIFY_SIZE(1536, "1.50KiB");
options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_ALWAYS;
VERIFY_SIZE(0, "0 B");
VERIFY_SIZE(1024, "1.00 KiB");
options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
VERIFY_SIZE(1024, "1.00 KiB");
}
// The result is appended to the buffer instead of replacing it
{
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateS("Size: ");
ffSizeAppendNum(1024, &result);
if (!ffStrbufEqualS(&result, "Size: 1.00 KiB")) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffSizeAppendNum did not append: got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, __LINE__, result.chars);
exit(1);
}
}
ffOptionsDestroyDisplay(options);
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}
+163 -1
View File
@@ -17,6 +17,168 @@ static void verify(bool expression, const char* expressionStr, int lineNo) {
#define VERIFY(expression) verify((expression), #expression, __LINE__)
int main(void) {
// The plain C string helpers. These have nothing to do with wcwidth, so they run in every build.
{
VERIFY(!ffStrSet(nullptr));
VERIFY(!ffStrSet(""));
VERIFY(!ffStrSet(" "));
VERIFY(!ffStrSet(" \t \n"));
VERIFY(ffStrSet("a"));
VERIFY(ffStrSet(" a"));
VERIFY(ffStrSet("a "));
}
{
VERIFY(ffStrStartsWith("", ""));
VERIFY(ffStrStartsWith("abc", ""));
VERIFY(ffStrStartsWith("abc", "a"));
VERIFY(ffStrStartsWith("abc", "abc"));
VERIFY(!ffStrStartsWith("abc", "abcd")); // the needle is longer than the haystack
VERIFY(!ffStrStartsWith("abc", "b"));
VERIFY(!ffStrStartsWith("", "a"));
VERIFY(!ffStrStartsWith("Abc", "abc"));
VERIFY(ffStrStartsWithIgnCase("Abc", "abc"));
VERIFY(ffStrStartsWithIgnCase("ABC", "abc"));
VERIFY(ffStrStartsWithIgnCase("abc", ""));
VERIFY(!ffStrStartsWithIgnCase("Abc", "abd"));
}
{
VERIFY(ffStrEndsWith("", ""));
VERIFY(ffStrEndsWith("abc", ""));
VERIFY(ffStrEndsWith("abc", "c"));
VERIFY(ffStrEndsWith("abc", "abc"));
VERIFY(!ffStrEndsWith("abc", "abcd")); // the needle is longer than the haystack
VERIFY(!ffStrEndsWith("abc", "b"));
VERIFY(!ffStrEndsWith("", "a"));
VERIFY(!ffStrEndsWith("abc", "Abc"));
VERIFY(ffStrEndsWithIgnCase("abc", "BC"));
VERIFY(ffStrEndsWithIgnCase("abc", ""));
VERIFY(!ffStrEndsWithIgnCase("abc", "BD"));
}
{
VERIFY(ffStrEquals("", ""));
VERIFY(ffStrEquals("abc", "abc"));
VERIFY(!ffStrEquals("abc", "abcd"));
VERIFY(!ffStrEquals("abc", "ABC"));
VERIFY(ffStrEqualsIgnCase("abc", "ABC"));
VERIFY(ffStrEqualsIgnCase("", ""));
VERIFY(!ffStrEqualsIgnCase("abc", "abd"));
VERIFY(ffStrContains("abc", ""));
VERIFY(ffStrContains("abc", "b"));
VERIFY(ffStrContains("abc", "abc"));
VERIFY(!ffStrContains("abc", "d"));
VERIFY(ffStrContainsIgnCase("abc", "B"));
VERIFY(!ffStrContainsIgnCase("abc", "D"));
// `strcasestr` reports an empty needle as a match, but the `StrStrIA` it is aliased to on
// Windows reports no match, so on Windows this disagrees with `ffStrContains`.
#ifndef _WIN32
VERIFY(ffStrContainsIgnCase("abc", ""));
#endif
VERIFY(ffStrContainsC("abc", 'b'));
VERIFY(!ffStrContainsC("abc", 'd'));
VERIFY(ffStrContainsC("abc", '\0')); // `strchr` reports the terminator as a match
}
{
VERIFY(ffCharIsEnglishAlphabet('a'));
VERIFY(ffCharIsEnglishAlphabet('z'));
VERIFY(ffCharIsEnglishAlphabet('A'));
VERIFY(ffCharIsEnglishAlphabet('Z'));
VERIFY(!ffCharIsEnglishAlphabet('0'));
VERIFY(!ffCharIsEnglishAlphabet('_'));
VERIFY(!ffCharIsEnglishAlphabet(' '));
VERIFY(ffCharIsDigit('0'));
VERIFY(ffCharIsDigit('9'));
VERIFY(!ffCharIsDigit('/')); // one below '0'
VERIFY(!ffCharIsDigit(':')); // one above '9'
VERIFY(!ffCharIsDigit('a'));
VERIFY(ffCharIsHexDigit('0'));
VERIFY(ffCharIsHexDigit('9'));
VERIFY(ffCharIsHexDigit('a'));
VERIFY(ffCharIsHexDigit('F'));
VERIFY(!ffCharIsHexDigit('g'));
VERIFY(!ffCharIsHexDigit('G'));
VERIFY(!ffCharIsHexDigit('/'));
VERIFY(!ffCharIsHexDigit('@'));
}
{
VERIFY(ffHexCharToInt('0') == 0);
VERIFY(ffHexCharToInt('9') == 9);
VERIFY(ffHexCharToInt('a') == 10);
VERIFY(ffHexCharToInt('f') == 15);
VERIFY(ffHexCharToInt('A') == 10);
VERIFY(ffHexCharToInt('F') == 15);
VERIFY(ffHexCharToInt('g') == -1);
VERIFY(ffHexCharToInt('G') == -1);
VERIFY(ffHexCharToInt('/') == -1); // one below '0'
VERIFY(ffHexCharToInt(':') == -1); // one above '9'
VERIFY(ffHexCharToInt('@') == -1); // one below 'A'
VERIFY(ffHexCharToInt('`') == -1); // one below 'a'
VERIFY(ffHexCharToInt(' ') == -1);
}
// ffStrCopy never writes more than `dstBufSiz` bytes and always terminates. It returns a pointer
// to the end of the copy, so consecutive calls can be chained.
{
char buffer[8];
memset(buffer, 'X', sizeof(buffer));
char* end = ffStrCopy(buffer, "abc", sizeof(buffer));
VERIFY(ffStrEquals(buffer, "abc"));
VERIFY(end == buffer + 3);
memset(buffer, 'X', sizeof(buffer));
end = ffStrCopy(buffer, "abcdef", sizeof(buffer));
VERIFY(ffStrEquals(buffer, "abcdef"));
VERIFY(end == buffer + 6);
// Filling the buffer exactly still leaves room for the terminator
char exact[4];
memset(exact, 'X', sizeof(exact));
end = ffStrCopy(exact, "abc", sizeof(exact));
VERIFY(ffStrEquals(exact, "abc"));
VERIFY(end == exact + 3);
// One byte short: the copy is truncated to `dstBufSiz - 1` bytes
char truncated[3];
memset(truncated, 'X', sizeof(truncated));
end = ffStrCopy(truncated, "abcdef", sizeof(truncated));
VERIFY(ffStrEquals(truncated, "ab"));
VERIFY(end == truncated + 2);
// A one byte buffer holds nothing but the terminator
char single[1];
single[0] = 'X';
end = ffStrCopy(single, "abc", sizeof(single));
VERIFY(single[0] == '\0');
VERIFY(end == single);
// A zero sized buffer is left completely alone
char untouched[1] = { 'X' };
end = ffStrCopy(untouched, "abc", 0);
VERIFY(untouched[0] == 'X');
VERIFY(end == untouched);
VERIFY(ffStrCopy(nullptr, "abc", 8) == nullptr);
}
// FF_STR stringifies its argument
{
VERIFY(ffStrEquals(FF_STR(1), "1"));
VERIFY(ffStrEquals(FF_STR(123), "123"));
}
#if FF_ENABLE_WCWIDTH
{
uint8_t width = 255;
@@ -102,7 +264,7 @@ int main(void) {
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
#else
puts("\033[33mTests skipped because wcwidth support is disabled." FASTFETCH_TEXT_MODIFIER_RESET);
puts("\033[33mwcwidth tests skipped because wcwidth support is disabled." FASTFETCH_TEXT_MODIFIER_RESET);
#endif
return 0;
}
+177
View File
@@ -0,0 +1,177 @@
#include "common/option.h"
#include "common/temps.h"
#include "common/textModifier.h"
#include "fastfetch.h"
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
static FFModuleArgs module;
static void verify(double celsius, FFColorRangeConfig config, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
ffTempsAppendNum(celsius, &result, config, &module);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffTempsAppendNum(%f, {%u, %u}): expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
lineNo, celsius, config.green, config.yellow, expected, result.chars);
exit(1);
}
}
#define VERIFY_TEMP(celsius, config, expected) verify((celsius), (config), (expected), __LINE__)
int main(void) {
// Use the real defaults rather than a hand built config, so a changed default is caught here too
ffOptionsInitDisplay(&instance.config.display);
FFOptionsDisplay* options = &instance.config.display;
ffOptionInitModuleArg(&module, "");
// Pin the threshold colors: the yellow and red defaults depend on whether the terminal is
// detected as a light theme, which is not something a test should depend on.
ffStrbufSetS(&options->tempColorGreen, "32");
ffStrbufSetS(&options->tempColorYellow, "33");
ffStrbufSetS(&options->tempColorRed, "31");
const FFColorRangeConfig config = { .green = 50, .yellow = 80 };
// An unknown temperature prints nothing at all, not even the unit
{
options->pipe = true;
VERIFY_TEMP(-DBL_MAX, config, "");
}
// The default unit is Celsius, with one digit after the point
{
options->pipe = true;
VERIFY_TEMP(0, config, "0.0°C");
VERIFY_TEMP(25.5, config, "25.5°C");
VERIFY_TEMP(-10, config, "-10.0°C");
VERIFY_TEMP(-273.15, config, "-273.1°C");
}
// tempNdigits
{
options->pipe = true;
options->tempNdigits = 0;
VERIFY_TEMP(25.4, config, "25°C");
VERIFY_TEMP(25.6, config, "26°C");
VERIFY_TEMP(0, config, "0°C");
options->tempNdigits = 2;
VERIFY_TEMP(25.5, config, "25.50°C");
options->tempNdigits = 1;
}
// Celsius and Fahrenheit only take a space before the unit when it is forced on, while Kelvin
// takes one by default. That asymmetry is deliberate and easy to break.
{
options->pipe = true;
options->tempNdigits = 2; // so that 25.5 + 273.15 prints exactly
options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
VERIFY_TEMP(25.5, config, "25.50°C");
options->tempUnit = FF_TEMPERATURE_UNIT_KELVIN;
VERIFY_TEMP(25.5, config, "298.65 K");
options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;
options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER;
VERIFY_TEMP(25.5, config, "25.50°C");
options->tempUnit = FF_TEMPERATURE_UNIT_KELVIN;
VERIFY_TEMP(25.5, config, "298.65K");
options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;
options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_ALWAYS;
VERIFY_TEMP(25.5, config, "25.50 °C");
options->tempUnit = FF_TEMPERATURE_UNIT_KELVIN;
VERIFY_TEMP(25.5, config, "298.65 K");
options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;
options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
options->tempNdigits = 1;
}
// Fahrenheit and Kelvin are conversions of the Celsius value that the thresholds were compared
// against, so the unit changes what is printed but never which band is picked.
{
options->pipe = true;
options->tempUnit = FF_TEMPERATURE_UNIT_FAHRENHEIT;
VERIFY_TEMP(0, config, "32.0°F");
VERIFY_TEMP(100, config, "212.0°F");
VERIFY_TEMP(37, config, "98.6°F");
VERIFY_TEMP(-40, config, "-40.0°F"); // the one point where the two scales meet
options->tempNdigits = 2;
options->tempUnit = FF_TEMPERATURE_UNIT_KELVIN;
VERIFY_TEMP(0, config, "273.15 K");
VERIFY_TEMP(100, config, "373.15 K");
VERIFY_TEMP(-273.15, config, "0.00 K");
options->tempNdigits = 1;
options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;
}
// Colors, with the same band rules as the percentage module: the threshold itself belongs to the
// lower band, and the reset sequence is always emitted when colors are on.
{
options->pipe = false;
VERIFY_TEMP(0, config, "\e[32m0.0°C\e[m");
VERIFY_TEMP(50, config, "\e[32m50.0°C\e[m");
VERIFY_TEMP(51, config, "\e[33m51.0°C\e[m");
VERIFY_TEMP(80, config, "\e[33m80.0°C\e[m");
VERIFY_TEMP(81, config, "\e[31m81.0°C\e[m");
VERIFY_TEMP(100, config, "\e[31m100.0°C\e[m");
// The color is chosen from the Celsius value, so a display unit that is not Celsius does not
// move the threshold
options->tempUnit = FF_TEMPERATURE_UNIT_FAHRENHEIT;
VERIFY_TEMP(81, config, "\e[31m177.8°F\e[m");
options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;
// An unknown temperature has no color at all
VERIFY_TEMP(-DBL_MAX, config, "");
// When `green > yellow` the bands are inverted, so a low value is the alarming one
const FFColorRangeConfig inverted = { .green = 80, .yellow = 50 };
VERIFY_TEMP(0, inverted, "\e[31m0.0°C\e[m");
VERIFY_TEMP(49, inverted, "\e[31m49.0°C\e[m");
VERIFY_TEMP(50, inverted, "\e[33m50.0°C\e[m");
VERIFY_TEMP(79, inverted, "\e[33m79.0°C\e[m");
VERIFY_TEMP(80, inverted, "\e[32m80.0°C\e[m");
VERIFY_TEMP(100, inverted, "\e[32m100.0°C\e[m");
}
// The color is restored to the module's output color, so the rest of the line keeps its own
{
options->pipe = false;
ffStrbufSetS(&module.outputColor, "94");
VERIFY_TEMP(0, config, "\e[32m0.0°C\e[m\e[94m");
ffStrbufClear(&module.outputColor);
// ... and it falls back to the global output color when the module has none
ffStrbufSetS(&options->colorOutput, "95");
VERIFY_TEMP(0, config, "\e[32m0.0°C\e[m\e[95m");
ffStrbufClear(&options->colorOutput);
}
// The result is appended to the buffer instead of replacing it
{
options->pipe = true;
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateS("Temp: ");
ffTempsAppendNum(25.5, &result, config, &module);
if (!ffStrbufEqualS(&result, "Temp: 25.5°C")) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffTempsAppendNum did not append: got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, __LINE__, result.chars);
exit(1);
}
}
ffOptionDestroyModuleArg(&module);
ffOptionsDestroyDisplay(options);
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}