White Owl
White Owl

Reputation: 1116

Redirect stdout in unit testing with ctest

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

Answers (1)

White Owl
White Owl

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

Related Questions