|
| 1 | +#include <furi.h> |
| 2 | +#include <errno.h> |
| 3 | +#include <stdio.h> |
| 4 | +#include "../test.h" // IWYU pragma: keep |
| 5 | + |
| 6 | +#define TAG "StdioTest" |
| 7 | + |
| 8 | +#define CONTEXT_MAGIC ((void*)0xDEADBEEF) |
| 9 | + |
| 10 | +// stdin |
| 11 | + |
| 12 | +static char mock_in[256]; |
| 13 | +static size_t mock_in_len, mock_in_pos; |
| 14 | + |
| 15 | +static void set_mock_in(const char* str) { |
| 16 | + size_t len = strlen(str); |
| 17 | + strcpy(mock_in, str); |
| 18 | + mock_in_len = len; |
| 19 | + mock_in_pos = 0; |
| 20 | +} |
| 21 | + |
| 22 | +static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) { |
| 23 | + UNUSED(wait); |
| 24 | + furi_check(context == CONTEXT_MAGIC); |
| 25 | + size_t remaining = mock_in_len - mock_in_pos; |
| 26 | + size = MIN(remaining, size); |
| 27 | + memcpy(buffer, mock_in + mock_in_pos, size); |
| 28 | + mock_in_pos += size; |
| 29 | + return size; |
| 30 | +} |
| 31 | + |
| 32 | +void test_stdin(void) { |
| 33 | + FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); |
| 34 | + furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC); |
| 35 | + char buf[256]; |
| 36 | + |
| 37 | + // plain in |
| 38 | + set_mock_in("Hello, World!\n"); |
| 39 | + fgets(buf, sizeof(buf), stdin); |
| 40 | + mu_assert_string_eq("Hello, World!\n", buf); |
| 41 | + mu_assert_int_eq(EOF, getchar()); |
| 42 | + |
| 43 | + // ungetc |
| 44 | + ungetc('i', stdin); |
| 45 | + ungetc('H', stdin); |
| 46 | + fgets(buf, sizeof(buf), stdin); |
| 47 | + mu_assert_string_eq("Hi", buf); |
| 48 | + mu_assert_int_eq(EOF, getchar()); |
| 49 | + |
| 50 | + // ungetc + plain in |
| 51 | + set_mock_in(" World"); |
| 52 | + ungetc('i', stdin); |
| 53 | + ungetc('H', stdin); |
| 54 | + fgets(buf, sizeof(buf), stdin); |
| 55 | + mu_assert_string_eq("Hi World", buf); |
| 56 | + mu_assert_int_eq(EOF, getchar()); |
| 57 | + |
| 58 | + // partial plain in |
| 59 | + set_mock_in("Hello, World!\n"); |
| 60 | + fgets(buf, strlen("Hello") + 1, stdin); |
| 61 | + mu_assert_string_eq("Hello", buf); |
| 62 | + mu_assert_int_eq(',', getchar()); |
| 63 | + fgets(buf, sizeof(buf), stdin); |
| 64 | + mu_assert_string_eq(" World!\n", buf); |
| 65 | + |
| 66 | + furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC); |
| 67 | +} |
| 68 | + |
| 69 | +// stdout |
| 70 | + |
| 71 | +static FuriString* mock_out; |
| 72 | +FuriThreadStdoutWriteCallback original_out_cb; |
| 73 | + |
| 74 | +static void mock_out_cb(const char* data, size_t size, void* context) { |
| 75 | + furi_check(context == CONTEXT_MAGIC); |
| 76 | + // there's no furi_string_cat_strn :( |
| 77 | + for(size_t i = 0; i < size; i++) { |
| 78 | + furi_string_push_back(mock_out, data[i]); |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +static void assert_and_clear_mock_out(const char* expected) { |
| 83 | + // return the original stdout callback for the duration of the check |
| 84 | + // if the check fails, we don't want the error to end up in our buffer, |
| 85 | + // we want to be able to see it! |
| 86 | + furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); |
| 87 | + mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); |
| 88 | + furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); |
| 89 | + |
| 90 | + furi_string_reset(mock_out); |
| 91 | +} |
| 92 | + |
| 93 | +void test_stdout(void) { |
| 94 | + original_out_cb = furi_thread_get_stdout_callback(); |
| 95 | + furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); |
| 96 | + mock_out = furi_string_alloc(); |
| 97 | + |
| 98 | + puts("Hello, World!"); |
| 99 | + assert_and_clear_mock_out("Hello, World!\n"); |
| 100 | + |
| 101 | + printf("He"); |
| 102 | + printf("llo!"); |
| 103 | + fflush(stdout); |
| 104 | + assert_and_clear_mock_out("Hello!"); |
| 105 | + |
| 106 | + furi_string_free(mock_out); |
| 107 | + furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); |
| 108 | +} |
0 commit comments