aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/moslib/safe.h13
-rw-r--r--include/moslib/tester.h34
-rw-r--r--src/safe.c34
-rw-r--r--src/tester.c68
-rw-r--r--tests/safe_tests.c67
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 <stddef.h>
+#include <stdio.h>
#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 <stdlib.h>
#include <unistd.h>
-#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
-#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 <limits.h>
#include <stdlib.h>
+#include <unistd.h>
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);
}