Reputation: 1116
Writing unit test with ctest
. The function to test is doing printout to stdout/stderr.
So, right now, I am having:
typedef struct {
char *stdout_file;
char *stderr_file;
} test_config;
static int group_setup(void **state) {
test_config *tc = malloc(sizeof(test_config));
tc->stdout_file = strdup("/tmp/new_stdout.txt");
tc->stderr_file = strdup("/tmp/new_stderr.txt");
*state = tc;
return 0;
}
static int setup(void **state) {
test_config *tc = *state;
if(!freopen(tc->stdout_file, "a+", stdout)) {
fprintf(stderr, "redirection failed to %s: %m", tc->stdout_file);
return -1;
}
if(!freopen(tc->stderr_file, "a+", stderr)) {
fprintf(stderr, "redirection failed to %s: %m", tc->stderr_file);
return -1;
}
return 0;
}
static int teardown(void **state) {
test_config *tc = *state;
/// How to restore stdout???
return 0;
}
static int group_teardown(void **state) {
test_config *tc = *state;
free(tc->stdout_file);
free(tc->stderr_file);
free(tc);
return 0;
}
static void test(void **state) {
function_to_test(); // it should always print something to stdout or stderr
char buffer[LINE_MAX];
FILE *out = fopen(tc->stdout_file, "r");
fread(buffer, 1, sizeof(buffer), out);
fclose(out);
int rc = memcmp(buffer, expected_output, strlen(expected_output));
assert_int_equal(rc, 0);
}
int main() {
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test, setup, teardown),
}
return cmocka_run_group_tests(tests, group_setup, group_teardown);
}
The issue I have is that the file used for redirection is not created. But message from freopen()
failure is not printed. In the LastTest.log I see:
[ RUN ] mytest
<end of output>
Which leads me to believe that the redirection actually happened. But where are the files?
And how can I return stdout/stderr back to ctest
? Why do not I see [ PASSED ] mytest
in the test log?
Is there a better way to do it? Should not ctest
already have some built-in ability to check contents of stdout? It is fairly common issue, isn't it?
Upvotes: 1
Views: 85
Reputation: 1116
Finally, I solved the issue like this:
static int setup(void **state) {
test_config *tc = *state;
// nothing to do with capturing TTY
return 0;
}
static int teardown(void **state) {
test_config *tc = *state;
unlink(tc->stdout_file);
unlink(tc->stderr_file);
return 0;
}
static void capture_tty(void **state) {
test_config *tc = *state;
fflush(stdout);
tc->stdout_fd = dup(STDOUT_FILENO);
int redir_fd = open(tc->stdout_file, O_WRONLY | O_CREAT, S_IREAD | S_IWRITE);
dup2(redir_fd, STDOUT_FILENO);
close(redir_fd);
// same for stderr
}
static void release_tty(void **state) {
test_config *tc = *state;
fflush(stdout);
dup2(tc->stdout_fd, STDOUT_FILENO);
close(tc->stdout_fd);
// same for stderr
}
static void test(void **state) {
capture_tty(state);
function_to_test(); // it should always print something to stdout or stderr
release_tty(state);
// at this moment, standard streams are back under ctest's control and
// all assert_*() work as expected.
}
That approach gives a full capture of stdout/stderr for just tested functions. While not interfering with ctest
's own capture.
Thank you guys for the hints.
Upvotes: 1