From 06954da1e847072f9e95df0f183d9327a0f05b5b Mon Sep 17 00:00:00 2001 From: Mosder Date: Fri, 21 Aug 2026 14:41:26 +0200 Subject: feat: added some for safe wrappers + added ability to capture and test whether output is what was expected --- include/moslib/safe.h | 13 ++++++++++ include/moslib/tester.h | 34 ++++++++++++++++++++++--- src/safe.c | 34 ++++++++++++++++++++++++- src/tester.c | 68 +++++++++++++++++++++++++++++++++++++------------ tests/safe_tests.c | 67 ++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 190 insertions(+), 26 deletions(-) diff --git a/include/moslib/safe.h b/include/moslib/safe.h index 8074c31..18ab5b0 100644 --- a/include/moslib/safe.h +++ b/include/moslib/safe.h @@ -3,6 +3,7 @@ // aka "I don't want to check for NULL every single time" header #include +#include #ifndef MOSLIB_SAFE_H #define MOSLIB_SAFE_H @@ -12,6 +13,10 @@ #define safe_malloc mos_safe_malloc #define safe_calloc mos_safe_calloc #define safe_realloc mos_safe_realloc +#define safe_fopen mos_safe_fopen +#define safe_tmpfile mos_safe_tmpfile +#define safe_dup mos_safe_dup +#define safe_dup2 mos_safe_dup2 #endif // MOS_FORCE_PREFIXES @@ -28,10 +33,18 @@ typedef struct { #define mos_safe_malloc(size) mos_safe_malloc_fn(size, mos_err_info) #define mos_safe_calloc(count, size) mos_safe_calloc_fn(count, size, mos_err_info) #define mos_safe_realloc(ptr, size) mos_safe_realloc_fn(ptr, size, mos_err_info) +#define mos_safe_fopen(path, mode) mos_safe_fopen_fn(path, mode, mos_err_info) +#define mos_safe_tmpfile() mos_safe_tmpfile_fn(mos_err_info) +#define mos_safe_dup(fd) mos_safe_dup_fn(fd, mos_err_info) +#define mos_safe_dup2(fd, fd2) mos_safe_dup2_fn(fd, fd2, mos_err_info) // Function prototypes for macros extern void *mos_safe_malloc_fn(size_t size, MosSafeErrInfo info); extern void *mos_safe_calloc_fn(size_t count, size_t size, MosSafeErrInfo info); extern void *mos_safe_realloc_fn(void *ptr, size_t size, MosSafeErrInfo info); +extern FILE *mos_safe_fopen_fn(const char *path, const char *mode, MosSafeErrInfo info); +extern FILE *mos_safe_tmpfile_fn(MosSafeErrInfo info); +extern int mos_safe_dup_fn(int fd, MosSafeErrInfo info); +extern int mos_safe_dup2_fn(int fd, int fd2, MosSafeErrInfo info); #endif // MOSLIB_SAFE_H diff --git a/include/moslib/tester.h b/include/moslib/tester.h index 9423684..a522b9d 100644 --- a/include/moslib/tester.h +++ b/include/moslib/tester.h @@ -46,12 +46,23 @@ // test_assert_exit(helper_test, EXIT_FAILURE); // } // +// You can also test if a function writes correct output. +// You need to create a helper test and pass it into test_assert_out in main test. +// Pass the helper test, output stream, and expected output to it. +// Example: +// Test helper_test() { +// printf("output"); +// } +// Test main_test() { +// test_assert_out(helper_test, stdout, "output"); +// } +// // If your functions have output and you want to suppress them for the sake of tests, // you can do so using suppress_output and unsupress_outputs. // Example: // Test error_test() { -// suppress_output(&stdout); -// suppress_output(&stderr); +// suppress_output(stdout); +// suppress_output(stderr); // int code = fun_that_fails_with_output(); // unsuppress_outputs(); // test_assert(code == 0, "function didn't fail"); @@ -93,6 +104,7 @@ #define add_test_group mos_add_test_group #define test_assert mos_test_assert #define test_assert_exit mos_test_assert_exit +#define test_assert_out mos_test_assert_out #define suppress_output mos_suppress_output #define unsuppress_outputs mos_unsuppress_outputs #define add_test mos_add_test @@ -148,12 +160,25 @@ extern void mos_test_assert(int expression, const char *fail_message); // expected exit code #define mos_test_assert_exit(function, code) mos_test_assert_exit_fn(function, code, #function); +// Assert that function writes correct output to specified stream +// +// Arguments: +// function +// function that is supposed to write output +// +// stream +// stream to write the output to +// +// output +// expected output of the function +#define mos_test_assert_out(function, stream, output) mos_test_assert_out_fn(function, stream, output, #function, #stream); + // Suppress given output // // Arguments: // out -// pointer to outpout to suppress -extern void mos_suppress_output(FILE **out); +// outpout to suppress +extern void mos_suppress_output(FILE *out); // Unuppress previously suppressed outputs // Will unsupress all of the outputs that were suppressed @@ -189,6 +214,7 @@ extern void mos_free_tester(MosTester *tester); // Function prototypes for macros extern MosTestGroup *mos_add_test_group_fn(MosTester *tester, const char *group_name); extern void mos_test_assert_exit_fn(MosTestFn function, int code, const char *name); +extern void mos_test_assert_out_fn(MosTestFn function, FILE *stream, const char *output, const char *fn_name, const char *stream_name); extern void mos_add_test_fn(MosTestGroup *group, MosTestFn test, const char *name); #endif // MOSLIB_TESTER_H diff --git a/src/safe.c b/src/safe.c index 7c7627e..6e22d0a 100644 --- a/src/safe.c +++ b/src/safe.c @@ -6,7 +6,7 @@ #include #include -#define ERR_MESS(msg, ...) err_mess(info, msg, __VA_ARGS__) +#define ERR_MESS(...) err_mess(info, __VA_ARGS__) void err_mess(MosSafeErrInfo info, const char *msg, ...) { va_list args; @@ -40,3 +40,35 @@ void *mos_safe_realloc_fn(void *ptr, size_t size, MosSafeErrInfo info) { ERR_MESS("failed to realloc %p to %zu bytes", ptr, size); exit(EXIT_FAILURE); } + +FILE *mos_safe_fopen_fn(const char *path, const char *mode, MosSafeErrInfo info) { + FILE *p = fopen(path, mode); + if (p) + return p; + ERR_MESS("failed to fopen %s with mode %s", path, mode); + exit(EXIT_FAILURE); +} + +FILE *mos_safe_tmpfile_fn(MosSafeErrInfo info) { + FILE *p = tmpfile(); + if (p) + return p; + ERR_MESS("failed to create a tmpfile"); + exit(EXIT_FAILURE); +} + +int mos_safe_dup_fn(int fd, MosSafeErrInfo info) { + int res = dup(fd); + if (res != -1) + return res; + ERR_MESS("failed to dup %d", fd); + exit(EXIT_FAILURE); +} + +int mos_safe_dup2_fn(int fd, int fd2, MosSafeErrInfo info) { + int res = dup2(fd, fd2); + if (res != -1) + return res; + ERR_MESS("failed to dup %d 2 %d", fd, fd2); + exit(EXIT_FAILURE); +} diff --git a/src/tester.c b/src/tester.c index 84c1ca6..39dfb1a 100644 --- a/src/tester.c +++ b/src/tester.c @@ -6,8 +6,9 @@ #include #include #include +#include -#include "moslib/mem/alloc.h" +#include "moslib/safe.h" #define INIT_GROUP_CAP 8 #define INIT_TEST_CAP 16 @@ -22,8 +23,8 @@ int failed = 0; FILE *dev_null = NULL; typedef struct { - FILE ***outs; - FILE **ogs; + int *outs; + int *ogs; size_t n_outs; size_t out_cap; } Outputs; @@ -128,31 +129,64 @@ void mos_test_assert_exit_fn(MosTestFn function, int code, const char *name) { } } -void mos_suppress_output(FILE **out) { +void mos_test_assert_out_fn(MosTestFn function, FILE *stream, const char *output, const char *fn_name, const char *stream_name) { + size_t n = strlen(output); + char *out = mos_safe_calloc(n + 2, 1); + + FILE *tmp = mos_safe_tmpfile(); + + int fd = fileno(stream); + int og = mos_safe_dup(fd); + fflush(stream); + mos_safe_dup2(fileno(tmp), fd); + + function(); + fflush(stream); + rewind(tmp); + fread(out, 1, n + 1, tmp); + fclose(tmp); + + mos_safe_dup2(og, fd); + close(og); + + if (strcmp(out, output)) { + failed = 1; + print_color("ASSERT FAILED: ", RED); + printf("function %s outputed to %s:\n", fn_name, stream_name); + printf(" %s (first %zu chars)\n", out, n + 1); + printf(" expected: %s\n", output); + } + free(out); +} + +void mos_suppress_output(FILE *out) { if (!suppressed.outs || suppressed.n_outs >= suppressed.out_cap) { while (suppressed.n_outs >= suppressed.out_cap) suppressed.out_cap *= 2; - suppressed.outs = mos_safe_realloc(suppressed.outs, suppressed.out_cap * sizeof(FILE **)); - suppressed.ogs = mos_safe_realloc(suppressed.ogs, suppressed.out_cap * sizeof(FILE *)); + suppressed.outs = mos_safe_realloc(suppressed.outs, suppressed.out_cap * sizeof(int)); + suppressed.ogs = mos_safe_realloc(suppressed.ogs, suppressed.out_cap * sizeof(int)); } - suppressed.outs[suppressed.n_outs] = out; - suppressed.ogs[suppressed.n_outs++] = *out; + suppressed.outs[suppressed.n_outs] = fileno(out); + suppressed.ogs[suppressed.n_outs++] = dup(fileno(out)); - if (!dev_null) { - dev_null = fopen("/dev/null", "w"); - if (!dev_null) - return; - } + if (!dev_null) + dev_null = mos_safe_fopen("/dev/null", "w"); - *out = dev_null; + fflush(out); + dup2(fileno(dev_null), fileno(out)); } void mos_unsuppress_outputs(void) { if (!suppressed.outs || !suppressed.ogs) return; - for (size_t i = 0; i < suppressed.n_outs; i++) - *suppressed.outs[i] = suppressed.ogs[i]; + if (dev_null) + fflush(dev_null); + + for (size_t i = suppressed.n_outs; i-- > 0;) { + dup2(suppressed.ogs[i], suppressed.outs[i]); + close(suppressed.ogs[i]); + } suppressed.n_outs = 0; } @@ -231,8 +265,10 @@ void mos_free_tester(MosTester *tester) { free(tester->groups); free(tester); } + mos_unsuppress_outputs(); if (dev_null) { fclose(dev_null); + dev_null = NULL; } if (suppressed.outs) { free(suppressed.outs); diff --git a/tests/safe_tests.c b/tests/safe_tests.c index 1d48a48..3d46a37 100644 --- a/tests/safe_tests.c +++ b/tests/safe_tests.c @@ -3,6 +3,7 @@ #include #include +#include Test malloc_exit() { safe_malloc(ULLONG_MAX); @@ -17,11 +18,6 @@ Test test_safe_malloc() { unsuppress_outputs(); } -Test realloc_exit() { - void *p = NULL; - safe_realloc(p, ULLONG_MAX); -} - Test calloc_exit() { safe_calloc(ULLONG_MAX, ULLONG_MAX); } @@ -35,6 +31,11 @@ Test test_safe_calloc() { unsuppress_outputs(); } +Test realloc_exit() { + void *p = NULL; + safe_realloc(p, ULLONG_MAX); +} + Test test_safe_realloc() { void *p = NULL; p = safe_realloc(p, 1024); @@ -48,9 +49,65 @@ Test test_safe_realloc() { unsuppress_outputs(); } +Test fopen_exit() { + safe_fopen("/non-existing-file-name", "r"); +} + +Test test_safe_fopen() { + FILE *fp = safe_fopen(__FILE__, "r"); + test_assert(fp != NULL, "Failed to fopen current file"); + + suppress_output(stderr); + test_assert_exit(fopen_exit, EXIT_FAILURE); + unsuppress_outputs(); +} + +Test test_safe_tmpfile() { + FILE *fp = safe_tmpfile(); + test_assert(fp != NULL, "Failed to create a tmpfile"); +} + +Test dup_exit() { + safe_dup(-1); +} + +Test test_safe_dup() { + int fd = safe_dup(STDOUT_FILENO); + test_assert(fd != -1, "Failed to dup STDOUT_FILENO"); + close(fd); + + suppress_output(stderr); + test_assert_exit(dup_exit, EXIT_FAILURE); + unsuppress_outputs(); +} + +Test dup2_exit() { + safe_dup2(-1, STDOUT_FILENO); +} + +Test test_safe_dup2() { + int res = dup2(STDOUT_FILENO, STDOUT_FILENO); + test_assert(res != -1, "Failed to dup STDOUT_FILENO 2 itself"); + + int fd = dup(STDOUT_FILENO); + res = dup2(STDERR_FILENO, STDOUT_FILENO); + test_assert(res != -1, "Failed to dup STDERR_FILENO 2 STDOUT_FILENO"); + res = dup2(fd, STDOUT_FILENO); + test_assert(res != -1, "Failed to restore STDOUT_FILENO"); + close(fd); + + suppress_output(stderr); + test_assert_exit(dup2_exit, EXIT_FAILURE); + unsuppress_outputs(); +} + void safe_tests(Tester *tester) { TestGroup *group = add_test_group(tester, "moslib/safe.h"); add_test(group, test_safe_malloc); add_test(group, test_safe_calloc); add_test(group, test_safe_realloc); + add_test(group, test_safe_fopen); + add_test(group, test_safe_tmpfile); + add_test(group, test_safe_dup); + add_test(group, test_safe_dup2); } -- cgit v1.2.3