|
| 1 | +/* LibC Test Suite - Comprehensive tests for standard library functions. |
| 2 | + * |
| 3 | + * Current Coverage: |
| 4 | + * - vsnprintf/snprintf: Buffer overflow protection |
| 5 | + * * C99 semantics, truncation behavior, ISR safety |
| 6 | + * * Format specifiers: %s, %d, %u, %x, %p, %c, %% |
| 7 | + * * Edge cases: size=0, size=1, truncation, null termination |
| 8 | + * |
| 9 | + * Future Tests (Planned): |
| 10 | + * - String functions: strlen, strcmp, strcpy, strncpy, memcpy, memset |
| 11 | + * - Memory allocation: malloc, free, realloc |
| 12 | + * - Character classification: isdigit, isalpha, isspace, etc. |
| 13 | + */ |
| 14 | + |
| 15 | +#include <linmo.h> |
| 16 | + |
| 17 | +#define TEST_PASS 1 |
| 18 | +#define TEST_FAIL 0 |
| 19 | + |
| 20 | +/* Test result tracking */ |
| 21 | +static int tests_run = 0; |
| 22 | +static int tests_passed = 0; |
| 23 | +static int tests_failed = 0; |
| 24 | + |
| 25 | +/* Simple string comparison for tests */ |
| 26 | +static int test_strcmp(const char *s1, const char *s2) |
| 27 | +{ |
| 28 | + while (*s1 && (*s1 == *s2)) |
| 29 | + s1++, s2++; |
| 30 | + return *s1 - *s2; |
| 31 | +} |
| 32 | + |
| 33 | +/* Simple string length for tests */ |
| 34 | +static size_t test_strlen(const char *s) |
| 35 | +{ |
| 36 | + const char *p = s; |
| 37 | + while (*p) |
| 38 | + p++; |
| 39 | + return p - s; |
| 40 | +} |
| 41 | + |
| 42 | +/* Test assertion macro */ |
| 43 | +#define ASSERT_TEST(condition, test_name) \ |
| 44 | + do { \ |
| 45 | + tests_run++; \ |
| 46 | + if (condition) { \ |
| 47 | + tests_passed++; \ |
| 48 | + printf("[PASS] %s\n", test_name); \ |
| 49 | + } else { \ |
| 50 | + tests_failed++; \ |
| 51 | + printf("[FAIL] %s\n", test_name); \ |
| 52 | + } \ |
| 53 | + } while (0) |
| 54 | + |
| 55 | +/* Test 1: Basic functionality with sufficient buffer */ |
| 56 | +void test_basic_functionality(void) |
| 57 | +{ |
| 58 | + char buf[64]; |
| 59 | + int ret; |
| 60 | + |
| 61 | + /* Test simple string */ |
| 62 | + ret = snprintf(buf, sizeof(buf), "Hello World"); |
| 63 | + ASSERT_TEST(ret == 11 && test_strcmp(buf, "Hello World") == 0, |
| 64 | + "Basic string formatting"); |
| 65 | + |
| 66 | + /* Test integer formatting */ |
| 67 | + ret = snprintf(buf, sizeof(buf), "Number: %d", 42); |
| 68 | + ASSERT_TEST(ret == 10 && test_strcmp(buf, "Number: 42") == 0, |
| 69 | + "Integer formatting"); |
| 70 | + |
| 71 | + /* Test unsigned formatting */ |
| 72 | + ret = snprintf(buf, sizeof(buf), "Unsigned: %u", 123); |
| 73 | + ASSERT_TEST(ret == 13 && test_strcmp(buf, "Unsigned: 123") == 0, |
| 74 | + "Unsigned formatting"); |
| 75 | + |
| 76 | + /* Test hex formatting */ |
| 77 | + ret = snprintf(buf, sizeof(buf), "Hex: %x", 0xDEAD); |
| 78 | + ASSERT_TEST(ret == 9 && test_strcmp(buf, "Hex: dead") == 0, |
| 79 | + "Hex formatting"); |
| 80 | + |
| 81 | + /* Test pointer formatting */ |
| 82 | + void *ptr = (void *) 0x12345678; |
| 83 | + ret = snprintf(buf, sizeof(buf), "Ptr: %p", ptr); |
| 84 | + ASSERT_TEST(ret == 13 && test_strcmp(buf, "Ptr: 12345678") == 0, |
| 85 | + "Pointer formatting"); |
| 86 | + |
| 87 | + /* Test character formatting */ |
| 88 | + ret = snprintf(buf, sizeof(buf), "Char: %c", 'A'); |
| 89 | + ASSERT_TEST(ret == 7 && test_strcmp(buf, "Char: A") == 0, |
| 90 | + "Character formatting"); |
| 91 | + |
| 92 | + /* Test multiple format specifiers */ |
| 93 | + ret = snprintf(buf, sizeof(buf), "%d %s %x", 42, "test", 0xFF); |
| 94 | + ASSERT_TEST(ret == 10 && test_strcmp(buf, "42 test ff") == 0, |
| 95 | + "Multiple format specifiers"); |
| 96 | +} |
| 97 | + |
| 98 | +/* Test 2: Edge case - size = 0 (C99 semantics) */ |
| 99 | +void test_size_zero(void) |
| 100 | +{ |
| 101 | + char buf[10] = "unchanged"; |
| 102 | + int ret; |
| 103 | + |
| 104 | + /* C99: should return length that would be written, no buffer modification |
| 105 | + */ |
| 106 | + ret = snprintf(buf, 0, "Hello World"); |
| 107 | + ASSERT_TEST(ret == 11 && test_strcmp(buf, "unchanged") == 0, |
| 108 | + "Size=0 preserves buffer"); |
| 109 | + |
| 110 | + /* NULL buffer with size=0 is valid (C99) */ |
| 111 | + ret = snprintf(NULL, 0, "Test %d", 123); |
| 112 | + ASSERT_TEST(ret == 8, "NULL buffer with size=0"); |
| 113 | +} |
| 114 | + |
| 115 | +/* Test 3: Edge case - size = 1 (only null terminator) */ |
| 116 | +void test_size_one(void) |
| 117 | +{ |
| 118 | + char buf[10]; |
| 119 | + int ret; |
| 120 | + |
| 121 | + buf[0] = 'X'; /* Sentinel value */ |
| 122 | + ret = snprintf(buf, 1, "Hello"); |
| 123 | + ASSERT_TEST(ret == 5 && buf[0] == '\0', |
| 124 | + "Size=1 writes only null terminator"); |
| 125 | +} |
| 126 | + |
| 127 | +/* Test 4: Truncation scenarios */ |
| 128 | +void test_truncation(void) |
| 129 | +{ |
| 130 | + char buf[10]; |
| 131 | + int ret; |
| 132 | + |
| 133 | + /* String longer than buffer */ |
| 134 | + ret = snprintf(buf, sizeof(buf), "This is a very long string"); |
| 135 | + ASSERT_TEST(ret == 26 && test_strlen(buf) == 9 && buf[9] == '\0', |
| 136 | + "Truncation with long string"); |
| 137 | + |
| 138 | + /* Exact fit (9 chars + null in 10-byte buffer) */ |
| 139 | + ret = snprintf(buf, sizeof(buf), "123456789"); |
| 140 | + ASSERT_TEST( |
| 141 | + ret == 9 && test_strcmp(buf, "123456789") == 0 && buf[9] == '\0', |
| 142 | + "Exact fit"); |
| 143 | + |
| 144 | + /* One char too long */ |
| 145 | + ret = snprintf(buf, sizeof(buf), "1234567890"); |
| 146 | + ASSERT_TEST( |
| 147 | + ret == 10 && test_strcmp(buf, "123456789") == 0 && buf[9] == '\0', |
| 148 | + "One char truncation"); |
| 149 | + |
| 150 | + /* Format string producing truncated output */ |
| 151 | + ret = snprintf(buf, 8, "Value: %d", 12345); |
| 152 | + ASSERT_TEST(ret == 12 && test_strcmp(buf, "Value: ") == 0 && buf[7] == '\0', |
| 153 | + "Format truncation"); |
| 154 | +} |
| 155 | + |
| 156 | +/* Test 5: Null-termination guarantee */ |
| 157 | +void test_null_termination(void) |
| 158 | +{ |
| 159 | + char buf[5]; |
| 160 | + int ret; |
| 161 | + |
| 162 | + /* Fill buffer to verify null-termination */ |
| 163 | + for (int i = 0; i < 5; i++) |
| 164 | + buf[i] = 'X'; |
| 165 | + |
| 166 | + ret = snprintf(buf, 5, "1234567890"); |
| 167 | + ASSERT_TEST(buf[4] == '\0', "Null termination guaranteed"); |
| 168 | + |
| 169 | + /* Verify buffer was limited */ |
| 170 | + ASSERT_TEST(test_strcmp(buf, "1234") == 0, "Truncated content correct"); |
| 171 | + |
| 172 | + /* C99 return value: chars that would be written */ |
| 173 | + ASSERT_TEST(ret == 10, "C99 return value for truncation"); |
| 174 | +} |
| 175 | + |
| 176 | +/* Test 6: Format specifier edge cases */ |
| 177 | +void test_format_specifiers(void) |
| 178 | +{ |
| 179 | + char buf[32]; |
| 180 | + int ret; |
| 181 | + |
| 182 | + /* Null string pointer */ |
| 183 | + ret = snprintf(buf, sizeof(buf), "String: %s", (char *) NULL); |
| 184 | + ASSERT_TEST(test_strcmp(buf, "String: <NULL>") == 0, |
| 185 | + "NULL string handling"); |
| 186 | + |
| 187 | + /* Negative numbers */ |
| 188 | + ret = snprintf(buf, sizeof(buf), "%d", -42); |
| 189 | + ASSERT_TEST(test_strcmp(buf, "-42") == 0, "Negative number formatting"); |
| 190 | + |
| 191 | + /* Zero */ |
| 192 | + ret = snprintf(buf, sizeof(buf), "%d %u %x", 0, 0, 0); |
| 193 | + ASSERT_TEST(test_strcmp(buf, "0 0 0") == 0, "Zero formatting"); |
| 194 | + |
| 195 | + /* Width padding */ |
| 196 | + ret = snprintf(buf, sizeof(buf), "%5d", 42); |
| 197 | + ASSERT_TEST(test_strcmp(buf, " 42") == 0, "Width padding"); |
| 198 | + |
| 199 | + /* Zero padding */ |
| 200 | + ret = snprintf(buf, sizeof(buf), "%05d", 42); |
| 201 | + ASSERT_TEST(test_strcmp(buf, "00042") == 0, "Zero padding"); |
| 202 | + |
| 203 | + /* Literal percent */ |
| 204 | + ret = snprintf(buf, sizeof(buf), "100%% complete"); |
| 205 | + ASSERT_TEST(test_strcmp(buf, "100% complete") == 0, "Literal percent sign"); |
| 206 | + |
| 207 | + (void) ret; /* Return values tested in test_return_values() */ |
| 208 | +} |
| 209 | + |
| 210 | +/* Test 7: C99 return value semantics */ |
| 211 | +void test_return_values(void) |
| 212 | +{ |
| 213 | + char buf[10]; |
| 214 | + int ret; |
| 215 | + |
| 216 | + /* Return value = chars that would be written (excluding null) */ |
| 217 | + ret = snprintf(buf, sizeof(buf), "12345"); |
| 218 | + ASSERT_TEST(ret == 5, "Return value for normal case"); |
| 219 | + |
| 220 | + /* Return value for truncated string */ |
| 221 | + ret = snprintf(buf, 5, "1234567890"); |
| 222 | + ASSERT_TEST(ret == 10, "Return value for truncated case"); |
| 223 | + |
| 224 | + /* Empty string */ |
| 225 | + ret = snprintf(buf, sizeof(buf), ""); |
| 226 | + ASSERT_TEST(ret == 0 && buf[0] == '\0', "Empty string return value"); |
| 227 | +} |
| 228 | + |
| 229 | +/* Test 8: Buffer boundary verification */ |
| 230 | +void test_buffer_boundaries(void) |
| 231 | +{ |
| 232 | + char buf[16]; |
| 233 | + char guard_before = 0xAA; |
| 234 | + char guard_after = 0xBB; |
| 235 | + int ret; |
| 236 | + |
| 237 | + /* Place guards around buffer */ |
| 238 | + char *test_buf = &buf[1]; |
| 239 | + buf[0] = guard_before; |
| 240 | + buf[15] = guard_after; |
| 241 | + |
| 242 | + /* Write to middle buffer, verify guards intact */ |
| 243 | + ret = snprintf(test_buf, 14, "Test boundary"); |
| 244 | + ASSERT_TEST(buf[0] == guard_before && buf[15] == guard_after, |
| 245 | + "No buffer overrun"); |
| 246 | + |
| 247 | + ASSERT_TEST(test_strcmp(test_buf, "Test boundary") == 0, |
| 248 | + "Content correct within boundaries"); |
| 249 | + |
| 250 | + (void) ret; /* Return value not critical for boundary test */ |
| 251 | +} |
| 252 | + |
| 253 | +/* Test 9: ISR safety verification */ |
| 254 | +void test_isr_safety(void) |
| 255 | +{ |
| 256 | + char buf[32]; |
| 257 | + int ret; |
| 258 | + |
| 259 | + /* Verify no dynamic allocation (manual inspection of code required) |
| 260 | + * Verify bounded execution time (all format handlers O(1) or O(n) small n) |
| 261 | + * Verify reentrancy (no global state modified) |
| 262 | + */ |
| 263 | + |
| 264 | + /* Test can be called multiple times without side effects */ |
| 265 | + ret = snprintf(buf, sizeof(buf), "ISR Test %d", 123); |
| 266 | + char saved[32]; |
| 267 | + for (int i = 0; i < 32; i++) |
| 268 | + saved[i] = buf[i]; |
| 269 | + |
| 270 | + ret = snprintf(buf, sizeof(buf), "ISR Test %d", 123); |
| 271 | + int match = 1; |
| 272 | + for (int i = 0; i < 32; i++) { |
| 273 | + if (buf[i] != saved[i]) { |
| 274 | + match = 0; |
| 275 | + break; |
| 276 | + } |
| 277 | + } |
| 278 | + ASSERT_TEST(match == 1, "Reentrant behavior (no global state)"); |
| 279 | + |
| 280 | + (void) ret; /* Return value not critical for ISR safety test */ |
| 281 | +} |
| 282 | + |
| 283 | +/* Test 10: Mixed format stress test */ |
| 284 | +void test_mixed_formats(void) |
| 285 | +{ |
| 286 | + char buf[128]; |
| 287 | + int ret; |
| 288 | + |
| 289 | + /* Complex format string with multiple types */ |
| 290 | + ret = snprintf(buf, sizeof(buf), |
| 291 | + "Task %d: ptr=%p, count=%u, hex=%x, char=%c, str=%s", 5, |
| 292 | + (void *) 0xABCD, 100, 0xFF, 'X', "test"); |
| 293 | + |
| 294 | + /* Verify no crash and reasonable return value */ |
| 295 | + ASSERT_TEST(ret > 0 && ret < 128, "Mixed format stress test"); |
| 296 | + |
| 297 | + /* Verify null termination */ |
| 298 | + ASSERT_TEST(buf[test_strlen(buf)] == '\0', "Mixed format null termination"); |
| 299 | +} |
| 300 | + |
| 301 | +void test_runner(void) |
| 302 | +{ |
| 303 | + printf("\n=== LibC Test Suite ===\n"); |
| 304 | + printf("Testing: vsnprintf/snprintf\n\n"); |
| 305 | + |
| 306 | + test_basic_functionality(); |
| 307 | + test_size_zero(); |
| 308 | + test_size_one(); |
| 309 | + test_truncation(); |
| 310 | + test_null_termination(); |
| 311 | + test_format_specifiers(); |
| 312 | + test_return_values(); |
| 313 | + test_buffer_boundaries(); |
| 314 | + test_isr_safety(); |
| 315 | + test_mixed_formats(); |
| 316 | + |
| 317 | + printf("\n=== Test Summary ===\n"); |
| 318 | + printf("Tests run: %d\n", tests_run); |
| 319 | + printf("Tests passed: %d\n", tests_passed); |
| 320 | + printf("Tests failed: %d\n", tests_failed); |
| 321 | + |
| 322 | + if (tests_failed == 0) { |
| 323 | + printf("\n[SUCCESS] All tests passed!\n"); |
| 324 | + } else { |
| 325 | + printf("\n[FAILURE] %d test(s) failed!\n", tests_failed); |
| 326 | + } |
| 327 | +} |
| 328 | + |
| 329 | +int32_t app_main(void) |
| 330 | +{ |
| 331 | + test_runner(); |
| 332 | + |
| 333 | + /* Shutdown QEMU cleanly after tests complete */ |
| 334 | + *(volatile uint32_t *) 0x100000U = 0x5555U; |
| 335 | + |
| 336 | + /* Fallback: keep task alive if shutdown fails (non-QEMU environments) */ |
| 337 | + while (1) |
| 338 | + ; /* Idle loop */ |
| 339 | + |
| 340 | + return 0; |
| 341 | +} |
0 commit comments