diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 57387e8..dbc99aa 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -15,7 +15,7 @@ jobs: - name: install packages run: sudo apt-get install -y --no-install-recommends --no-install-suggests meson ninja-build - libevdev-dev liburing-dev + liburing-dev libwayland-dev libwayland-client0 wayland-protocols - name: configure diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..30d2178 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: install packages + run: sudo apt-get install -y --no-install-recommends --no-install-suggests + meson ninja-build + liburing-dev + libwayland-dev libwayland-client0 wayland-protocols + + - name: configure + run: meson setup build --buildtype release -Dtest=true + + - name: test + run: meson test -C build diff --git a/include/assert.h b/include/assert.h new file mode 100644 index 0000000..2ef33a9 --- /dev/null +++ b/include/assert.h @@ -0,0 +1,15 @@ +#pragma once + +#if GAMEPAD_IDLE_INHIBIT_DEBUG +#define debug_assert(x) \ + if (!(x)) { \ + __builtin_debugtrap(); \ + } +#else +#define debug_assert(x) +#endif + +#define runtime_assert(x) \ + if (!(x)) { \ + __builtin_trap(); \ + } diff --git a/include/math.h b/include/math.h new file mode 100644 index 0000000..0e3b00b --- /dev/null +++ b/include/math.h @@ -0,0 +1,57 @@ +#pragma once + +#include "type.h" + +/* + * Returns binary logarithm of 𝑥. + * + * ctz(𝑥) 31^clz(𝑥) clz(𝑥) + * uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) + * 0x00000000 wut 32 0 wut 32 + * 0x00000001 0 0 1 0 31 + * 0x80000001 0 0 1 31 0 + * 0x80000000 31 31 32 31 0 + * 0x00000010 4 4 5 4 27 + * 0x08000010 4 4 5 27 4 + * 0x08000000 27 27 28 27 4 + * 0xffffffff 0 0 1 31 0 + * + * @param x is a 64-bit integer + * @return number in range 0..63 or undefined if 𝑥 is 0 + * + * @note Adapted from https://github.com/jart/cosmopolitan/blob/master/libc/intrin/bsrl.c + * @copyright + * ╒══════════════════════════════════════════════════════════════════════════════╕ + * │ Copyright 2023 Justine Alexandra Roberts Tunney │ + * │ │ + * │ Permission to use, copy, modify, and/or distribute this software for │ + * │ any purpose with or without fee is hereby granted, provided that the │ + * │ above copyright notice and this permission notice appear in all copies. │ + * │ │ + * │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ + * │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ + * │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ + * │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ + * │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ + * │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ + * │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ + * │ PERFORMANCE OF THIS SOFTWARE. │ + * └──────────────────────────────────────────────────────────────────────────────┘ + */ +static inline u8 +bsrl(s64 x) +{ + static const u8 kDebruijn[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, + 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, + 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63, + }; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x |= x >> 32; + return kDebruijn[(x * 0x03f79d71b4cb0a89ull) >> 58]; +} diff --git a/include/text.h b/include/text.h new file mode 100644 index 0000000..3f5d92b --- /dev/null +++ b/include/text.h @@ -0,0 +1,421 @@ +#pragma once + +#include "assert.h" +#include "math.h" +#include "type.h" + +struct string { + u8 *value; + u64 length; +}; + +static inline struct string +StringFromZeroTerminated(u8 *src, u64 max) +{ + debug_assert(src != 0); + struct string string = {}; + + string.value = src; + + while (*src) { + string.length++; + if (string.length == max) + break; + src++; + } + + return string; +} + +static inline b8 +IsStringEqual(struct string *left, struct string *right) +{ + if (!left || !right || left->length != right->length) + return 0; + + for (u64 index = 0; index < left->length; index++) { + if (left->value[index] != right->value[index]) + return 0; + } + + return 1; +} + +static inline b8 +IsStringContains(struct string *string, struct string *search) +{ + if (!string || !search || string->length < search->length) + return 0; + + for (u64 stringIndex = 0; stringIndex < string->length; stringIndex++) { + b8 isFound = 1; + for (u64 searchIndex = 0, substringIndex = stringIndex; searchIndex < search->length; + searchIndex++, substringIndex++) { + b8 isEndOfString = substringIndex == string->length; + if (isEndOfString) { + isFound = 0; + break; + } + + b8 isCharactersNotMatching = string->value[substringIndex] != search->value[searchIndex]; + if (isCharactersNotMatching) { + isFound = 0; + break; + } + } + + if (isFound) + return 1; + } + + return 0; +} + +static inline b8 +IsStringStartsWith(struct string *string, struct string *search) +{ + if (!string || !search || string->length < search->length) + return 0; + + for (u64 searchIndex = 0; searchIndex < search->length; searchIndex++) { + b8 isCharactersNotMatching = string->value[searchIndex] != search->value[searchIndex]; + if (isCharactersNotMatching) + return 0; + } + + return 1; +} + +struct duration { + u64 ns; +}; + +static inline b8 +ParseDuration(struct string *string, struct duration *duration) +{ + if (!string || string->length == 0 || string->length < 3) + return 0; + + // | Duration | Length | + // |----------|-------------| + // | ns | nanosecond | + // | us | microsecond | + // | ms | millisecond | + // | sec | second | + // | min | minute | + // | hr | hour | + // | day | day | + // | wk | week | + +#define UNIT_STRING(variableName, zeroTerminatedString) \ + static struct string variableName = { \ + .value = (u8 *)zeroTerminatedString, \ + .length = sizeof(zeroTerminatedString) - 1, \ + } + UNIT_STRING(nanosecondUnitString, "ns"); + UNIT_STRING(microsecondUnitString, "us"); + UNIT_STRING(millisocondUnitString, "ms"); + UNIT_STRING(secondUnitString, "sec"); + UNIT_STRING(minuteUnitString, "min"); + UNIT_STRING(hourUnitString, "hr"); + UNIT_STRING(dayUnitString, "day"); + UNIT_STRING(weekUnitString, "wk"); +#undef UNIT_STRING + + b8 isUnitExistsInString = + IsStringContains(string, &nanosecondUnitString) || IsStringContains(string, µsecondUnitString) || + IsStringContains(string, &millisocondUnitString) || IsStringContains(string, &secondUnitString) || + IsStringContains(string, &minuteUnitString) || IsStringContains(string, &hourUnitString) || + IsStringContains(string, &dayUnitString) || IsStringContains(string, &weekUnitString); + if (!isUnitExistsInString) { + return 0; + } + + struct duration parsed = {}; + u64 value = 0; + for (u64 index = 0; index < string->length; index++) { + u8 digitCharacter = string->value[index]; + b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; + if (!isDigit) { + // - get unit + struct string unitString = {.value = string->value + index, .length = string->length - index}; + if (/* unit: nanosecond */ IsStringStartsWith(&unitString, &nanosecondUnitString)) { + parsed.ns += value; + index += nanosecondUnitString.length; + } else if (/* unit: microsecond */ IsStringStartsWith(&unitString, µsecondUnitString)) { + parsed.ns += value * 1e3L; + index += microsecondUnitString.length; + } else if (/* unit: millisecond */ IsStringStartsWith(&unitString, &millisocondUnitString)) { + parsed.ns += value * 1e6L; + index += millisocondUnitString.length; + } else if (/* unit: second */ IsStringStartsWith(&unitString, &secondUnitString)) { + parsed.ns += value * 1e9L; + index += secondUnitString.length; + } else if (/* unit: minute */ IsStringStartsWith(&unitString, &minuteUnitString)) { + parsed.ns += value * 1e9L * 60; + index += minuteUnitString.length; + } else if (/* unit: hour */ IsStringStartsWith(&unitString, &hourUnitString)) { + parsed.ns += value * 1e9L * 60 * 60; + index += hourUnitString.length; + } else if (/* unit: day */ IsStringStartsWith(&unitString, &dayUnitString)) { + parsed.ns += value * 1e9L * 60 * 60 * 24; + index += dayUnitString.length; + } else if (/* unit: week */ IsStringStartsWith(&unitString, &weekUnitString)) { + parsed.ns += value * 1e9L * 60 * 60 * 24 * 7; + index += weekUnitString.length; + } else { + // unsupported unit + return 0; + } + + // - reset value + value = 0; + continue; + } + + value *= 10; + u8 digit = digitCharacter - (u8)'0'; + value += digit; + } + + *duration = parsed; + + return 1; +} + +static inline b8 +IsDurationLessThan(struct duration *left, struct duration *right) +{ + return left->ns < right->ns; +} + +static inline b8 +IsDurationGraterThan(struct duration *left, struct duration *right) +{ + return left->ns > right->ns; +} + +static inline b8 +ParseU64(struct string *string, u64 *value) +{ + // max u64: 18446744073709551615 + if (!string || string->length > 20) + return 0; + + u64 parsed = 0; + for (u64 index = 0; index < string->length; index++) { + u8 digitCharacter = string->value[index]; + b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; + if (!isDigit) { + return 0; + } + + parsed *= 10; + u8 digit = digitCharacter - (u8)'0'; + parsed += digit; + } + + *value = parsed; + return 1; +} + +/* + * string buffer must at least able to hold 1 bytes, at most 20 bytes. + */ +static inline struct string +FormatU64(struct string *stringBuffer, u64 value) +{ + struct string result = {}; + if (!stringBuffer || stringBuffer->length == 0) + return result; + + // max u64: 18446744073709551615 + static const u64 powersOf10[20] = { + 1e00L, 1e01L, 1e02L, 1e03L, 1e04L, 1e05L, 1e06L, 1e07L, 1e08L, 1e09L, + 1e10L, 1e11L, 1e12L, 1e13L, 1e14L, 1e15L, 1e16L, 1e17L, 1e18L, 1e19L, + }; + u64 countOfDigits = 1; + while (countOfDigits < 20 && value > powersOf10[countOfDigits]) + countOfDigits++; + + if (countOfDigits > stringBuffer->length) + return result; + + u64 index = 0; + while (countOfDigits > 0) { + u64 power = powersOf10[countOfDigits - 1]; + u64 digit = value / power; + + // turn digit into character + stringBuffer->value[index] = digit + (u8)'0'; + + value -= digit * power; + + index++; + countOfDigits--; + } + + result.value = stringBuffer->value; + result.length = index; // written digits + return result; +} + +static inline struct string +FormatS64(struct string *stringBuffer, s64 value) +{ + struct string result = {}; + if (!stringBuffer || stringBuffer->length == 0) + return result; + + b8 isNegativeValue = value < 0; + if (isNegativeValue) { + value *= -1; + stringBuffer->value[0] = '-'; + stringBuffer->value += 1; + stringBuffer->length -= 1; + } + + result = FormatU64(stringBuffer, value); + return result; +} + +/* + * string buffer must at least able to hold 3 bytes. + * fractionCount [1,8] + */ +static inline struct string +FormatF32(struct string *stringBuffer, f32 value, u32 fractionCount) +{ + debug_assert(fractionCount >= 1 && fractionCount <= 8); + + struct string result = {}; + if (!stringBuffer || stringBuffer->length <= 3) + return result; + + // 1 - convert integer part to string + // assume value: 10.123 + // integerValue: 10 + struct string stringBufferForInteger = { + .value = stringBuffer->value, + .length = stringBuffer->length, + }; + + b8 isNegativeValue = value < 0; + if (isNegativeValue) { + value *= -1; + stringBufferForInteger.value[0] = '-'; + stringBufferForInteger.value += 1; + stringBufferForInteger.length -= 1; + } + + u32 integerValue = (u32)value; + struct string integerString = FormatU64(&stringBufferForInteger, (u64)integerValue); + if (integerString.length == 0) + return result; + + // 2 - insert point + stringBufferForInteger.value[integerString.length] = '.'; + + // 3 - convert fraction to string + struct string stringBufferForFraction = { + .value = stringBufferForInteger.value + integerString.length + 1, + .length = stringBufferForInteger.length - (integerString.length + 1), + }; + + // assume fractionCount = 2 + // 0.123 = 10.123 - 10.000 + // 12.30 = 0.123 * (10 ^ fractionCount) + // 12 = (int)12.30 + u64 fractionMultiplier = 10; + for (u32 fractionIndex = 1; fractionIndex < fractionCount; fractionIndex++) + fractionMultiplier *= 10; + + u32 fractionValue = (u32)((f32)(value - (f32)integerValue) * (f32)fractionMultiplier); + struct string fractionString = FormatU64(&stringBufferForFraction, fractionValue); + if (fractionString.length == 0) + return result; + + result.value = stringBuffer->value; + result.length = isNegativeValue + integerString.length + 1 + fractionString.length; + return result; +} + +/* + * + * Converts unsigned 64-bit integer to hex string. + * + * @param stringBuffer needs at least 18 bytes + * @return sub string from stringBuffer, returns 0 on string.value on failure + * + * @note Adapted from https://github.com/jart/cosmopolitan/blob/master/libc/intrin/formathex64.c + * @copyright + * ╒══════════════════════════════════════════════════════════════════════════════╕ + * │ Copyright 2023 Justine Alexandra Roberts Tunney │ + * │ │ + * │ Permission to use, copy, modify, and/or distribute this software for │ + * │ any purpose with or without fee is hereby granted, provided that the │ + * │ above copyright notice and this permission notice appear in all copies. │ + * │ │ + * │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ + * │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ + * │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ + * │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ + * │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ + * │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ + * │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ + * │ PERFORMANCE OF THIS SOFTWARE. │ + * └──────────────────────────────────────────────────────────────────────────────┘ + */ +static inline struct string +FormatHex(struct string *stringBuffer, u64 value) +{ + struct string result = {}; + if (!stringBuffer || stringBuffer->length < 18) + return result; + + if (value == 0) { + // edge case 0x00 + u8 *digit = stringBuffer->value; + *digit++ = '0'; + *digit++ = 'x'; + *digit++ = '0'; + *digit++ = '0'; + result.value = stringBuffer->value; + result.length = 4; + return result; + } + + u64 index = 0; + stringBuffer->value[index] = '0'; + index++; + stringBuffer->value[index] = 'x'; + index++; + + // 1 - pick good width + u64 width; + { + u8 n = bsrl(value); + if (n < 16) { + if (n < 8) + width = 8; + else + width = 16; + } else { + if (n < 32) + width = 32; + else + width = 64; + } + } + + // 2 - turn value into hex + do { + width -= 4; + stringBuffer->value[index] = "0123456789abcdef"[(value >> width) & 15]; + index++; + } while (width); + + result.value = stringBuffer->value; + result.length = index; + return result; +} diff --git a/include/type.h b/include/type.h new file mode 100644 index 0000000..af48bfa --- /dev/null +++ b/include/type.h @@ -0,0 +1,16 @@ +#pragma once + +typedef __INT8_TYPE__ s8; +typedef __INT16_TYPE__ s16; +typedef __INT32_TYPE__ s32; +typedef __INT64_TYPE__ s64; + +typedef __UINT8_TYPE__ u8; +typedef __UINT16_TYPE__ u16; +typedef __UINT32_TYPE__ u32; +typedef __UINT64_TYPE__ u64; +typedef u8 b8; + +typedef float f32; +typedef double f64; + diff --git a/meson.build b/meson.build index 7eaf538..23e3eb6 100644 --- a/meson.build +++ b/meson.build @@ -34,9 +34,14 @@ sources = files([ executable( 'gamepad_idle_inhibit', sources: sources + wl_protocols_src, + include_directories: 'include', dependencies: [ liburing, wayland_client, ], install: true, ) + +if get_option('test') + subdir('test') +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..604b860 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('test', type: 'boolean', value: true) diff --git a/src/main.c b/src/main.c index 915b187..d91625d 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,10 @@ #include #include +#include "assert.h" +#include "text.h" +#include "type.h" + // NOTE: CI fix #ifndef IORING_ASYNC_CANCEL_ALL #define IORING_ASYNC_CANCEL_ALL (1U << 0) @@ -23,20 +27,6 @@ #error alloca must be supported #endif -#if GAMEPAD_IDLE_INHIBIT_DEBUG -#define debug_assert(x) \ - if (!(x)) { \ - __builtin_debugtrap(); \ - } -#else -#define debug_assert(x) -#endif - -#define runtime_assert(x) \ - if (!(x)) { \ - __builtin_trap(); \ - } - #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) #include "idle-inhibit-unstable-v1-client-protocol.h" @@ -45,20 +35,6 @@ #define POLLPRI 0x002 /* There is urgent data to read. */ #define POLLOUT 0x004 /* Writing now will not block. */ -typedef __INT8_TYPE__ s8; -typedef __INT16_TYPE__ s16; -typedef __INT32_TYPE__ s32; -typedef __INT64_TYPE__ s64; - -typedef __UINT8_TYPE__ u8; -typedef __UINT16_TYPE__ u16; -typedef __UINT32_TYPE__ u32; -typedef __UINT64_TYPE__ u64; -typedef u8 b8; - -typedef float f32; -typedef double f64; - #define OP_DEVICE_OPEN (1 << 0) #define OP_JOYSTICK_POLL (1 << 1) #define OP_JOYSTICK_READ (1 << 2) @@ -273,8 +249,6 @@ wl_registry_global_remove(void *data, struct wl_registry *wl_registry, u32 name) static const struct wl_registry_listener wl_registry_listener = {.global = wl_registry_global, .global_remove = wl_registry_global_remove}; -#include "text.h" - int main(int argc, char *argv[]) { @@ -290,12 +264,12 @@ main(int argc, char *argv[]) // parse commandline arguments for (u64 argumentIndex = 1; argumentIndex < argc; argumentIndex++) { - struct string argument = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + struct string argument = StringFromZeroTerminated((u8 *)argv[argumentIndex], 1024); #define ARGUMENT_STRING(variableName, zeroTerminatedString) \ static struct string variableName = { \ - .data = (u8 *)zeroTerminatedString, \ - .len = sizeof(zeroTerminatedString) - 1, \ + .value = (u8 *)zeroTerminatedString, \ + .length = sizeof(zeroTerminatedString) - 1, \ } ARGUMENT_STRING(argumentTimeoutString, "--timeout"); ARGUMENT_STRING(argumentMaxGamepadCountString, "--max-gamepad-count"); @@ -312,7 +286,7 @@ main(int argc, char *argv[]) } argumentIndex++; - struct string timeoutString = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + struct string timeoutString = StringFromZeroTerminated((u8 *)argv[argumentIndex], 1024); struct duration parsed; struct duration oneSecond = (struct duration){.ns = 1 * 1e9L}; struct duration oneDay = (struct duration){.ns = 60 * 60 * 24 * 1 * 1e9L}; @@ -340,7 +314,7 @@ main(int argc, char *argv[]) } argumentIndex++; - struct string maxGamepadCountString = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + struct string maxGamepadCountString = StringFromZeroTerminated((u8 *)argv[argumentIndex], 1024); if (!ParseU64(&maxGamepadCountString, (u64 *)&maxGamepadCount)) { fatal("max-gamepad-count must be positive number\n"); return GAMEPAD_ERROR_ARGUMENT_MUST_BE_POSITIVE_NUMBER; @@ -385,18 +359,18 @@ main(int argc, char *argv[]) " How many gamepads need to be tracked.\n" \ " Default is 4." \ "\n\n" - .data = (u8 *)HELP_STRING_TEXT, - .len = sizeof(HELP_STRING_TEXT) - 1, + .value = (u8 *)HELP_STRING_TEXT, + .length = sizeof(HELP_STRING_TEXT) - 1, #undef HELP_STRING_TEXT }; - write(STDOUT_FILENO, helpString.data, helpString.len); + write(STDOUT_FILENO, helpString.value, helpString.length); return 0; } // unknown argument else { write(STDERR_FILENO, "e: Unknown '", 12); - write(STDERR_FILENO, argument.data, argument.len); + write(STDERR_FILENO, argument.value, argument.length); write(STDERR_FILENO, "' argument\n", 11); return GAMEPAD_ERROR_ARGUMENT_UNKNOWN; } @@ -452,9 +426,38 @@ main(int argc, char *argv[]) struct memory_chunk *MemoryForJoystickReadEvents = mem_push_chunk(&memory_block, sizeof(struct op_joystick_read), maxGamepadCount); + struct string stdoutBuffer = { + .value = mem_push(&memory_block, 256), + .length = 256, + }; + struct string stringBuffer = { + .value = mem_push(&memory_block, 32), + .length = 32, + }; + #if GAMEPAD_IDLE_INHIBIT_DEBUG - printf("total memory usage (in bytes): %lu\n", memory_block.used); - printf("total memory wasted (in bytes): %lu\n", memory_block.total - memory_block.used); + { + struct string string; + u64 length = 0; + +#define PRINTLN_U64(prefix, number) \ + string = (struct string){.value = (u8 *)prefix, .length = sizeof(prefix) - 1}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatU64(&stringBuffer, number); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = (struct string){.value = (u8 *)"\n", .length = 1}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + + PRINTLN_U64("total memory usage (in bytes): ", memory_block.used); + PRINTLN_U64("total memory wasted (in bytes): ", memory_block.total - memory_block.used); +#undef PRINTLN_U64 + + // print buffered string to output + write(STDOUT_FILENO, stdoutBuffer.value, length); + } #endif /* io_uring */ @@ -585,7 +588,41 @@ main(int argc, char *argv[]) stagedOp.triggerRange = triggerAbsInfo.maximum - triggerAbsInfo.minimum; stagedOp.triggerMinimum = triggerAbsInfo.minimum; - printf("Input device ID: bus %#x vendor %#x product %#x\n", id.bustype, id.vendor, id.product); + { + struct string string; + u64 length = 0; + + string = (struct string){.value = (u8 *)"Input device ID: bus ", .length = 21}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.bustype); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = (struct string){.value = (u8 *)" vendor ", .length = 8}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.vendor); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = (struct string){.value = (u8 *)" product ", .length = 9}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.product); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = (struct string){.value = (u8 *)"\n", .length = 1}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + write(STDOUT_FILENO, stdoutBuffer.value, length); + } + stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxGamepadCount); if (!stagedOp.gamepad) { warning("Maximum number of gamepads connected! So not registering this one.\n"); @@ -801,7 +838,41 @@ main(int argc, char *argv[]) stagedOp.triggerRange = triggerAbsInfo.maximum - triggerAbsInfo.minimum; stagedOp.triggerMinimum = triggerAbsInfo.minimum; - printf("Input device ID: bus %#x vendor %#x product %#x\n", id.bustype, id.vendor, id.product); + { + struct string string; + u64 length = 0; + + string = (struct string){.value = (u8 *)"Input device ID: bus ", .length = 21}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.bustype); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = (struct string){.value = (u8 *)" vendor ", .length = 8}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.vendor); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = (struct string){.value = (u8 *)" product ", .length = 9}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.product); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = (struct string){.value = (u8 *)"\n", .length = 1}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + write(STDOUT_FILENO, stdoutBuffer.value, length); + } + stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxGamepadCount); if (!stagedOp.gamepad) { warning("Maximum number of gamepads connected! So not registering this one.\n"); @@ -923,8 +994,11 @@ main(int argc, char *argv[]) struct input_event *event = &op->event; #if GAMEPAD_IDLE_INHIBIT_DEBUG - printf("%p fd: %d time: %ld.%ld type: %d code: %d value: %d\n", op, op_joystick_poll->fd, event->input_event_sec, - event->input_event_usec, event->type, event->code, event->value); + // LEFTOFF: bug hunting + // 1 - pressing lt,rt on gamepad gives assertion error when normalizing event->value + // 2 - moving ls,rs on gamepad changes gamepad to invalid address SIGSEGV + printf("%p fd: %d time: %ldsec%ldus type: %d code: %d value: %d\n", op, op_joystick_poll->fd, + event->input_event_sec, event->input_event_usec, event->type, event->code, event->value); #endif struct gamepad *gamepad = op_joystick_poll->gamepad; if (event->type == EV_SYN) { @@ -1028,24 +1102,84 @@ main(int argc, char *argv[]) } } - printf("Gamepad #%p a: %d b: %d x: %d y: %d ls: %d %.2f,%.2f rs: %d %.2f,%.2f lb: %d lt: %.2f rb: %d rt: %.2f " - "home: %d back: %d start: %d\n", - // pointer - gamepad, - // buttons - gamepad->a, gamepad->b, gamepad->x, gamepad->y, - // left stick - gamepad->ls, gamepad->lsX, gamepad->lsY, - // right stick - gamepad->rs, gamepad->rsX, gamepad->rsY, - // left button, left trigger - gamepad->lb, gamepad->lt, - // right button, right trigger - gamepad->rb, gamepad->rt, - // buttons - gamepad->home, gamepad->back, gamepad->start); + struct string string; + u64 length = 0; + + string = (struct string){.value = (u8 *)"Gamepad #", .length = 9}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + +#define PRINT_BUTTON(prefix, button) \ + string = (struct string){.value = (u8 *)(" " prefix ": "), .length = sizeof(prefix) - 1 + 3}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatU64(&stringBuffer, gamepad->button); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + +#define PRINT_ANALOG(prefix, stick, stickX, stickY) \ + string = (struct string){.value = (u8 *)(" " prefix ": "), .length = sizeof(prefix) - 1 + 3}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatU64(&stringBuffer, gamepad->stick); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = (struct string){.value = (u8 *)" ", .length = 1}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatF32(&stringBuffer, gamepad->stickX, 2); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = (struct string){.value = (u8 *)",", .length = 1}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatF32(&stringBuffer, gamepad->stickY, 2); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + +#define PRINT_TRIGGER(prefix, trigger) \ + string = (struct string){.value = (u8 *)(" " prefix ": "), .length = sizeof(prefix) - 1 + 3}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatF32(&stringBuffer, gamepad->trigger, 2); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + + PRINT_BUTTON("a", a); + PRINT_BUTTON("b", b); + PRINT_BUTTON("x", x); + PRINT_BUTTON("y", y); + + PRINT_ANALOG("ls", ls, lsX, lsY); + PRINT_ANALOG("rs", rs, rsX, rsY); + + PRINT_BUTTON("lb", lb); + PRINT_TRIGGER("lt", lt); + + PRINT_BUTTON("rb", rb); + PRINT_TRIGGER("rt", rt); + + PRINT_BUTTON("home", home); + PRINT_BUTTON("back", back); + PRINT_BUTTON("start", start); + + string = (struct string){.value = (u8 *)"\n", .length = 1}; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + write(STDOUT_FILENO, stdoutBuffer.value, length); + +#undef PRINT_BUTTON +#undef PRINT_ANALOG +#undef PRINT_TRIGGER } + // DEBUG: get file path from file descriptor + // char path[256]; + // snprintf(path, sizeof(path), "/proc/self/fd/%d", op_joystick_poll->fd); + // readlink(path, path, sizeof(path)); + // printf("fd: %d path: %s\n", op_joystick_poll->fd, path); + // NOTE: When there is more data to read // 1 - Try to read another joystick event struct op_joystick_read *submitOp = op; diff --git a/src/text.h b/src/text.h deleted file mode 100644 index 1d3bf26..0000000 --- a/src/text.h +++ /dev/null @@ -1,325 +0,0 @@ -#ifndef __TEXT_H__ -#define __TEXT_H__ - -struct string { - u8 *data; - u64 len; -}; - -static inline struct string -StringFromZeroTerminated(u8 *string) -{ - debug_assert(string != 0); - u64 len = strnlen((char *)string, 1024); - return (struct string){ - .data = string, - .len = len, - }; -} - -static inline b8 -IsStringEqual(struct string *left, struct string *right) -{ - if (!left || !right || left->len != right->len) - return 0; - - for (u64 index = 0; index < left->len; index++) { - if (left->data[index] != right->data[index]) - return 0; - } - - return 1; -} - -static inline b8 -IsStringContains(struct string *string, struct string *search) -{ - if (!string || !search || string->len < search->len) - return 0; - - /* TEST CODE - struct string string; - struct string search; - b8 result; - - string = (struct string){.data = (u8 *)"abc def ghi", .len = 11}; - search = (struct string){.data = (u8 *)"abc", .len = 3}; - debug_assert(IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"def", .len = 3}; - debug_assert(IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"ghi", .len = 3}; - debug_assert(IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"ghijkl", .len = 6}; - debug_assert(!IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"jkl", .len = 3}; - debug_assert(!IsStringContains(&string, &search)); - */ - - for (u64 stringIndex = 0; stringIndex < string->len; stringIndex++) { - b8 isFound = 1; - for (u64 searchIndex = 0, substringIndex = stringIndex; searchIndex < search->len; - searchIndex++, substringIndex++) { - b8 isEndOfString = substringIndex == string->len; - if (isEndOfString) { - isFound = 0; - break; - } - - b8 isCharactersNotMatching = string->data[substringIndex] != search->data[searchIndex]; - if (isCharactersNotMatching) { - isFound = 0; - break; - } - } - - if (isFound) - return 1; - } - - return 0; -} - -static inline b8 -IsStringStartsWith(struct string *string, struct string *search) -{ - if (!string || !search || string->len < search->len) - return 0; - - /* TEST CODE - struct string string; - struct string search; - - string = (struct string){.data = (u8 *)"abc def ghi", .len = 11}; - search = (struct string){.data = (u8 *)"abc", .len = 3}; - debug_assert(IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"def", .len = 3}; - debug_assert(!IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"ghi", .len = 3}; - debug_assert(!IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"ghijkl", .len = 6}; - debug_assert(!IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"jkl", .len = 3}; - debug_assert(!IsStringStartsWith(&string, &search)); - */ - - for (u64 searchIndex = 0; searchIndex < search->len; searchIndex++) { - b8 isCharactersNotMatching = string->data[searchIndex] != search->data[searchIndex]; - if (isCharactersNotMatching) - return 0; - } - - return 1; -} - -struct duration { - u64 ns; -}; - -static inline b8 -ParseDuration(struct string *string, struct duration *duration) -{ - if (!string || string->len == 0 || string->len < 3) - return 0; - - /* TEST CODE - struct string string; - struct duration duration; - b8 isParsingSuccessful; - - string = (struct string){.data = (u8 *)"1ns", .len = 3}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 1); - - string = (struct string){.data = (u8 *)"1sec", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 1 * 1e9L); - - string = (struct string){.data = (u8 *)"5sec", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L); - - string = (struct string){.data = (u8 *)"5min", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L * 60); - - string = (struct string){.data = (u8 *)"5day", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L * 60 * 60 * 24); - - string = (struct string){.data = (u8 *)"1hr5min", .len = 7}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == (1 * 1e9L * 60 * 60) + (5 * 1e9L * 60)); - - string = (struct string){.data = (u8 *)0, .len = 0}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)"", .len = 0}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)" ", .len = 1}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)"abc", .len = 3}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)"5m5s", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful) - */ - - // | Duration | Length | - // |----------|-------------| - // | ns | nanosecond | - // | us | microsecond | - // | ms | millisecond | - // | sec | second | - // | min | minute | - // | hr | hour | - // | day | day | - // | wk | week | - -#define UNIT_STRING(variableName, zeroTerminatedString) \ - static struct string variableName = { \ - .data = (u8 *)zeroTerminatedString, \ - .len = sizeof(zeroTerminatedString) - 1, \ - } - UNIT_STRING(nanosecondUnitString, "ns"); - UNIT_STRING(microsecondUnitString, "us"); - UNIT_STRING(millisocondUnitString, "ms"); - UNIT_STRING(secondUnitString, "sec"); - UNIT_STRING(minuteUnitString, "min"); - UNIT_STRING(hourUnitString, "hr"); - UNIT_STRING(dayUnitString, "day"); - UNIT_STRING(weekUnitString, "wk"); -#undef UNIT_STRING - - b8 isUnitExistsInString = - IsStringContains(string, &nanosecondUnitString) || IsStringContains(string, µsecondUnitString) || - IsStringContains(string, &millisocondUnitString) || IsStringContains(string, &secondUnitString) || - IsStringContains(string, &minuteUnitString) || IsStringContains(string, &hourUnitString) || - IsStringContains(string, &dayUnitString) || IsStringContains(string, &weekUnitString); - if (!isUnitExistsInString) { - return 0; - } - - struct duration parsed = {}; - u64 value = 0; - for (u64 index = 0; index < string->len; index++) { - u8 digitCharacter = string->data[index]; - b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; - if (!isDigit) { - // - get unit - struct string unitString = {.data = string->data + index, .len = string->len - index}; - if (/* unit: nanosecond */ IsStringStartsWith(&unitString, &nanosecondUnitString)) { - parsed.ns += value; - index += nanosecondUnitString.len; - } else if (/* unit: microsecond */ IsStringStartsWith(&unitString, µsecondUnitString)) { - parsed.ns += value * 1e3L; - index += microsecondUnitString.len; - } else if (/* unit: millisecond */ IsStringStartsWith(&unitString, &millisocondUnitString)) { - parsed.ns += value * 1e6L; - index += millisocondUnitString.len; - } else if (/* unit: second */ IsStringStartsWith(&unitString, &secondUnitString)) { - parsed.ns += value * 1e9L; - index += secondUnitString.len; - } else if (/* unit: minute */ IsStringStartsWith(&unitString, &minuteUnitString)) { - parsed.ns += value * 1e9L * 60; - index += minuteUnitString.len; - } else if (/* unit: hour */ IsStringStartsWith(&unitString, &hourUnitString)) { - parsed.ns += value * 1e9L * 60 * 60; - index += hourUnitString.len; - } else if (/* unit: day */ IsStringStartsWith(&unitString, &dayUnitString)) { - parsed.ns += value * 1e9L * 60 * 60 * 24; - index += dayUnitString.len; - } else if (/* unit: week */ IsStringStartsWith(&unitString, &weekUnitString)) { - parsed.ns += value * 1e9L * 60 * 60 * 24 * 7; - index += weekUnitString.len; - } else { - // unsupported unit - return 0; - } - - // - reset value - value = 0; - continue; - } - - value *= 10; - u8 digit = digitCharacter - (u8)'0'; - value += digit; - } - - *duration = parsed; - - return 1; -} - -/* TEST CODE - struct duration left; - struct duration right; - - left = (struct duration){.ns = 1 * 1e9L}; - right = (struct duration){.ns = 5 * 1e9L}; - debug_assert(IsDurationLessThen(&left, &right)); - debug_assert(!IsDurationGraterThan(&left, &right)); - - left = (struct duration){.ns = 1 * 1e9L}; - right = (struct duration){.ns = 1 * 1e9L}; - debug_assert(!IsDurationLessThan(&left, &right)); - debug_assert(!IsDurationGraterThan(&left, &right)); - - left = (struct duration){.ns = 5 * 1e9L}; - right = (struct duration){.ns = 1 * 1e9L}; - debug_assert(!IsDurationLessThan(&left, &right)); - debug_assert(IsDurationGraterThan(&left, &right)); - - return 0; - */ -static inline b8 -IsDurationLessThan(struct duration *left, struct duration *right) -{ - return left->ns < right->ns; -} - -static inline b8 -IsDurationGraterThan(struct duration *left, struct duration *right) -{ - return left->ns > right->ns; -} - -static inline b8 -ParseU64(struct string *string, u64 *value) -{ - if (!string) - return 0; - - u64 parsed = 0; - for (u64 index = 0; index < string->len; index++) { - u8 digitCharacter = string->data[index]; - b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; - if (!isDigit) { - return 1; - } - - parsed *= 10; - u8 digit = digitCharacter - (u8)'0'; - parsed += digit; - } - - *value = parsed; - return 1; -} - -#endif /* __TEXT_H__ */ diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..e887ded --- /dev/null +++ b/test/meson.build @@ -0,0 +1,2 @@ +t = executable('text_test', 'text_test.c', include_directories: '../include') +test('text', t) diff --git a/test/text_test.c b/test/text_test.c new file mode 100644 index 0000000..9ea5073 --- /dev/null +++ b/test/text_test.c @@ -0,0 +1,473 @@ +#include "text.h" + +// TODO: Show error pretty error message when a test fails +enum text_test_error { + TEXT_TEST_ERROR_NONE = 0, + TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED, + TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED_TRUNCATED, + TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_TRUE, + TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_FALSE, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_1, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_2, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_3, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_1, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_2, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_TRUE, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_1, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_2, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_3, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_4, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1NS, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1SEC, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_5SEC, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_7MIN, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1HR5MIN, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_10DAY, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NULL, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_EMPTY, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_SPACE, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NO_DURATION_STRING, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_WRONG_DURATION_NAMES, + TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_TRUE, + TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_FALSE, + TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_TRUE, + TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_FALSE, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_0, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_1, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_3912, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_18446744073709551615, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_0_9, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_1_0, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_50, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_55, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_0_9, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_1_0, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_50, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_55, + + // src: https://mesonbuild.com/Unit-tests.html#skipped-tests-and-hard-errors + // For the default exitcode testing protocol, the GNU standard approach in this case is to exit the program with error + // code 77. Meson will detect this and report these tests as skipped rather than failed. This behavior was added in + // version 0.37.0. + MESON_TEST_SKIP = 77, + // In addition, sometimes a test fails set up so that it should fail even if it is marked as an expected failure. The + // GNU standard approach in this case is to exit the program with error code 99. Again, Meson will detect this and + // report these tests as ERROR, ignoring the setting of should_fail. This behavior was added in version 0.50.0. + MESON_TEST_FAILED_TO_SET_UP = 99, +}; + +int +main(void) +{ + enum text_test_error errorCode = TEXT_TEST_ERROR_NONE; + + // StringFromZeroTerminated + { + char *input = "abc"; + struct string result = StringFromZeroTerminated((u8 *)input, 1024); + + char *expectedValue = input; + u64 expectedLength = 3; + if ((char *)result.value != expectedValue || result.length != expectedLength) { + errorCode = TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED; + goto end; + } + } + + { + char *input = "abcdefghijklm"; + struct string result = StringFromZeroTerminated((u8 *)input, 3); + + char *expectedValue = input; + u64 expectedLength = 3; + if (result.length != expectedLength || result.value != (u8 *)expectedValue) { + errorCode = TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED_TRUNCATED; + goto end; + } + } + + // IsStringEqual(struct string *left, struct string *right) + { + struct string leftString; + struct string rightString; + b8 expected; + + leftString = (struct string){.value = (u8 *)"abc", .length = 3}; + rightString = (struct string){.value = (u8 *)"abc", .length = 3}; + expected = 1; + if (IsStringEqual(&leftString, &rightString) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_TRUE; + goto end; + } + + rightString = (struct string){.value = (u8 *)"abc def ghi", .length = 11}; + expected = 0; + if (IsStringEqual(&leftString, &rightString) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_FALSE; + goto end; + } + } + + // IsStringContains(struct string *string, struct string *search) + { + struct string string; + struct string search; + b8 expected; + + string = (struct string){.value = (u8 *)"abc def ghi", .length = 11}; + search = (struct string){.value = (u8 *)"abc", .length = 3}; + expected = 1; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_1; + goto end; + } + + search = (struct string){.value = (u8 *)"def", .length = 3}; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_2; + goto end; + } + + search = (struct string){.value = (u8 *)"ghi", .length = 3}; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_3; + goto end; + } + + search = (struct string){.value = (u8 *)"ghijkl", .length = 6}; + expected = 0; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_1; + goto end; + } + + search = (struct string){.value = (u8 *)"jkl", .length = 3}; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_2; + goto end; + } + } + + // IsStringStartsWith(struct string *string, struct string *search) + { + struct string string; + struct string search; + b8 expected; + + string = (struct string){.value = (u8 *)"abc def ghi", .length = 11}; + search = (struct string){.value = (u8 *)"abc", .length = 3}; + expected = 1; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_TRUE; + goto end; + } + + search = (struct string){.value = (u8 *)"def", .length = 3}; + expected = 0; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_1; + goto end; + } + + search = (struct string){.value = (u8 *)"ghi", .length = 3}; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_2; + goto end; + } + + search = (struct string){.value = (u8 *)"ghijkl", .length = 6}; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_3; + goto end; + } + + search = (struct string){.value = (u8 *)"jkl", .length = 3}; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_4; + goto end; + } + } + + // ParseDuration(struct string *string, struct duration *duration) + { + struct string string; + struct duration duration; + b8 value; + b8 expected; + u64 expectedDurationInNs; + + string = (struct string){.value = (u8 *)"1ns", .length = 3}; + expected = 1; + expectedDurationInNs = 1; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1NS; + goto end; + } + + string = (struct string){.value = (u8 *)"1sec", .length = 4}; + expected = 1; + expectedDurationInNs = 1 * 1e9L; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1SEC; + goto end; + } + + string = (struct string){.value = (u8 *)"5sec", .length = 4}; + expectedDurationInNs = 5 * 1e9L; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_5SEC; + goto end; + } + + string = (struct string){.value = (u8 *)"7min", .length = 4}; + expectedDurationInNs = 7 * 1e9L * 60; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_7MIN; + goto end; + } + + string = (struct string){.value = (u8 *)"1hr5min", .length = 7}; + expectedDurationInNs = (1 * 1e9L * 60 * 60) + (5 * 1e9L * 60); + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1HR5MIN; + goto end; + } + + string = (struct string){.value = (u8 *)"10day", .length = 5}; + expectedDurationInNs = 10 * 1e9L * 60 * 60 * 24; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_10DAY; + goto end; + } + + string = (struct string){.value = (u8 *)0, .length = 0}; + expected = 0; + expectedDurationInNs = 0; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NULL; + goto end; + } + + string = (struct string){.value = (u8 *)"", .length = 0}; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_EMPTY; + goto end; + } + + string = (struct string){.value = (u8 *)" ", .length = 1}; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_SPACE; + goto end; + } + + string = (struct string){.value = (u8 *)"abc", .length = 3}; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NO_DURATION_STRING; + goto end; + } + + string = (struct string){.value = (u8 *)"5m5s", .length = 4}; + value = ParseDuration(&string, &duration); + if (value != expected && duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_WRONG_DURATION_NAMES; + goto end; + } + } + + // IsDurationLessThan(struct duration *left, struct duration *right) + // IsDurationGraterThan(struct duration *left, struct duration *right) + { + struct duration left; + struct duration right; + b8 expectedLessThan; + b8 expectedGraterThan; + + left = (struct duration){.ns = 1 * 1e9L}; + right = (struct duration){.ns = 5 * 1e9L}; + expectedLessThan = 1; + if (IsDurationLessThan(&left, &right) != expectedLessThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_TRUE; + goto end; + } + expectedGraterThan = 0; + if (IsDurationGraterThan(&left, &right) != expectedGraterThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_FALSE; + goto end; + } + + left = (struct duration){.ns = 1 * 1e9L}; + right = (struct duration){.ns = 1 * 1e9L}; + expectedLessThan = 0; + if (IsDurationLessThan(&left, &right) != expectedLessThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_FALSE; + goto end; + } + expectedGraterThan = 0; + if (IsDurationGraterThan(&left, &right) != expectedGraterThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_FALSE; + goto end; + } + + left = (struct duration){.ns = 5 * 1e9L}; + right = (struct duration){.ns = 1 * 1e9L}; + expectedLessThan = 0; + if (IsDurationLessThan(&left, &right) != expectedLessThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_FALSE; + goto end; + } + expectedGraterThan = 1; + if (IsDurationGraterThan(&left, &right) != expectedGraterThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_TRUE; + goto end; + } + } + + // FormatU64(struct string *stringBuffer, u64 value) + { + u8 buf[20]; + struct string stringBuffer = {.value = buf, .length = sizeof(buf)}; + struct string expected; + struct string value; + + value = FormatU64(&stringBuffer, 0); + expected = (struct string){.value = (u8 *)"0", .length = 1}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_0; + goto end; + } + + value = FormatU64(&stringBuffer, 1); + expected = (struct string){.value = (u8 *)"1", .length = 1}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_0; + goto end; + } + + value = FormatU64(&stringBuffer, 3912); + expected = (struct string){.value = (u8 *)"3912", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_3912; + goto end; + } + + value = FormatU64(&stringBuffer, 18446744073709551615UL); + expected = (struct string){.value = (u8 *)"18446744073709551615", .length = 20}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_18446744073709551615; + goto end; + } + } + + // FormatF32(struct string *stringBuffer, f32 value, u32 fractionCount) + { + u8 buf[20]; + struct string stringBuffer = {.value = buf, .length = sizeof(buf)}; + struct string expected; + struct string value; + + value = FormatF32(&stringBuffer, 0.99f, 1); + expected = (struct string){.value = (u8 *)"0.9", .length = 3}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_0_9; + goto end; + } + + value = FormatF32(&stringBuffer, 1.0f, 1); + expected = (struct string){.value = (u8 *)"1.0", .length = 3}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_1_0; + goto end; + } + + value = FormatF32(&stringBuffer, 2.50f, 2); + expected = (struct string){.value = (u8 *)"2.50", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_50; + goto end; + } + + value = FormatF32(&stringBuffer, 2.55999f, 2); + expected = (struct string){.value = (u8 *)"2.55", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_55; + goto end; + } + + value = FormatF32(&stringBuffer, -0.99f, 1); + expected = (struct string){.value = (u8 *)"-0.9", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_0_9; + goto end; + } + + value = FormatF32(&stringBuffer, -1.0f, 2); + expected = (struct string){.value = (u8 *)"-1.0", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_1_0; + goto end; + } + + value = FormatF32(&stringBuffer, -2.50f, 2); + expected = (struct string){.value = (u8 *)"-2.50", .length = 5}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_50; + goto end; + } + + value = FormatF32(&stringBuffer, -2.55999f, 2); + expected = (struct string){.value = (u8 *)"-2.55", .length = 5}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_55; + goto end; + } + } + + // FormatHex(struct string *stringBuffer, u64 value) + { + u8 buf[18]; + struct string stringBuffer = {.value = buf, .length = sizeof(buf)}; + struct string expected; + struct string value; + + value = FormatHex(&stringBuffer, 0x0); + expected = (struct string){.value = (u8 *)"0x00", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + + value = FormatHex(&stringBuffer, 0x4); + expected = (struct string){.value = (u8 *)"0x04", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + + value = FormatHex(&stringBuffer, 0x00f2aa499b9028eaul); + expected = (struct string){.value = (u8 *)"0x00f2aa499b9028ea", .length = 18}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + + value = FormatHex(&stringBuffer, 0x00f2aa499b9028eaul); + expected = (struct string){.value = (u8 *)"0x00f2aa499b9028ea", .length = 18}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + } + +end: + return (int)errorCode; +}