diff --git a/include/criterion/assert.h b/include/criterion/assert.h index 62c578c..6d50a1c 100644 --- a/include/criterion/assert.h +++ b/include/criterion/assert.h @@ -61,6 +61,7 @@ enum criterion_assert_messages { CRITERION_ASSERT_MSG_IS_NOT_NULL, CRITERION_ASSERT_MSG_IS_EMPTY, CRITERION_ASSERT_MSG_IS_NOT_EMPTY, + CRITERION_ASSERT_MSG_FILE_STR_MATCH, }; CR_BEGIN_C_API @@ -80,7 +81,7 @@ CR_END_C_API # endif # define CR_TRANSLATE_DEF_MSG__(Arg) \ - CR_EXPAND Arg + CR_IDENTITY Arg # define CR_TRANSLATE_DEF_MSG_(...) \ CR_EXPAND(translate_assert_msg( \ diff --git a/include/criterion/preprocess.h b/include/criterion/preprocess.h index 822e0e3..aee370a 100644 --- a/include/criterion/preprocess.h +++ b/include/criterion/preprocess.h @@ -33,6 +33,7 @@ # endif # define CR_EXPAND(x) x +# define CR_IDENTITY(...) __VA_ARGS__ # define CR_STR(x) CR_EXPAND(CR_STR_(x)) # define CR_STR_(x) #x diff --git a/include/criterion/redirect.h b/include/criterion/redirect.h index bb5bc6c..cf1efcf 100644 --- a/include/criterion/redirect.h +++ b/include/criterion/redirect.h @@ -25,6 +25,13 @@ # define CRITERION_REDIRECT_H_ # include "common.h" +# include "assert.h" + +# ifdef __cplusplus +# include +# else +# include +# endif CR_BEGIN_C_API @@ -32,10 +39,38 @@ CR_API void cr_redirect_stdout(void); CR_API void cr_redirect_stderr(void); CR_API void cr_redirect_stdin(void); -CR_API FILE* cr_get_redirected_stdout(void); -CR_API FILE* cr_get_redirected_stderr(void); -CR_API FILE* cr_get_redirected_stdin(void); +CR_API CR_STDN FILE* cr_get_redirected_stdout(void); +CR_API CR_STDN FILE* cr_get_redirected_stderr(void); +CR_API CR_STDN FILE* cr_get_redirected_stdin(void); + +CR_API int cr_file_match_str(CR_STDN FILE* f, const char *str); CR_END_C_API +# define cr_assert_redir_op_(Fail, Fun, Op, File, Str, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + !(Fun((File), (Str)) Op 0), \ + dummy, \ + CRITERION_ASSERT_MSG_FILE_STR_MATCH, \ + (CR_STR(File), Str), \ + __VA_ARGS__ \ + )) + +# define cr_assert_redir_op_va_(Fail, Fun, Op, ...) \ + CR_EXPAND(cr_assert_redir_op_( \ + Fail, \ + Fun, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)) \ + )) + +# define cr_assert_file_contents_match_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, ==, __VA_ARGS__)) +# define cr_expect_file_contents_match_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, ==, __VA_ARGS__)) + +# define cr_assert_file_contents_not_match_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, !=, __VA_ARGS__)) +# define cr_expect_file_contents_not_match_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, !=, __VA_ARGS__)) + #endif /* !CRITERION_REDIRECT_H_ */ diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 038f8c2..f792c1d 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -15,6 +15,7 @@ set(SAMPLES description.c simple.c theories.c + redirect.c signal.cc report.cc diff --git a/src/i18n.c b/src/i18n.c index b600399..fb6e7d7 100644 --- a/src/i18n.c +++ b/src/i18n.c @@ -17,6 +17,12 @@ char *translate_assert_msg(int msg_index, ...) { [CRITERION_ASSERT_MSG_IS_NOT_NULL] = N_("%s is not null."), [CRITERION_ASSERT_MSG_IS_EMPTY] = N_("%s is empty."), [CRITERION_ASSERT_MSG_IS_NOT_EMPTY] = N_("%s is not empty."), + +#ifdef ENABLE_NLS + [CRITERION_ASSERT_MSG_FILE_STR_MATCH] = N_("The file contents of %1$s does not match the string \"%2$s\"."), +#else + [CRITERION_ASSERT_MSG_FILE_STR_MATCH] = "The file contents of %s does not match the string \"%s\".", +#endif }; va_list vl; diff --git a/src/posix-compat.c b/src/posix-compat.c index 55a506f..45cf897 100644 --- a/src/posix-compat.c +++ b/src/posix-compat.c @@ -453,8 +453,72 @@ cr_std_fd get_std_fd(int fd_kind) { return kinds[fd_kind]; } -void cr_redirect(int fd_kind, s_pipe_handle *pipe, int fd_index) { - if (stdpipe_stack(pipe) < 0) +FILE* get_std_file(int fd_kind) { + switch (fd_kind) { + case CR_STDIN: return stdin; + case CR_STDOUT: return stdout; + case CR_STDERR: return stderr; + } + return NULL; +} + +int make_redirect_pipe(s_pipe_handle *handle, int noblock) { +#ifdef VANILLA_WIN32 + static char pipe_name[256] = {0}; + + HANDLE fhs[2]; + SECURITY_ATTRIBUTES attr = { + .nLength = sizeof (SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE + }; + if (!pipe_name[0]) { + snprintf(pipe_name, sizeof (pipe_name), + "\\\\.\\pipe\\criterion_%d", GetCurrentProcessId()); + } + fhs[0] = CreateNamedPipe(pipe_name, + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE + | (noblock ? PIPE_NOWAIT : PIPE_WAIT), + PIPE_UNLIMITED_INSTANCES + 4096 * 4, + 4096 * 4, + 0, + attr); + + if (fds[0] == INVALID_HANDLE_VALUE) + return 0; + + fhs[1] = CreateFile(pipe_name, + GENERIC_READ | GENERIC_WRITE, + 0, + attr, + OPEN_EXISTING, + 0, + NULL); + + if (fds[1] == INVALID_HANDLE_VALUE) { + CloseHandle(fds[0]); + return 0; + } + + *handle = (s_pipe_handle) {{ fhs[0], fhs[1] }}; +#else + int fds[2] = { -1, -1 }; + if (pipe(fds) == -1) + return 0; + + if (noblock) + for (int i = 0; i < 2; ++i) + fcntl(fds[i], F_SETFL, fcntl(fds[i], F_GETFL) | O_NONBLOCK); + + *handle = (s_pipe_handle) {{ fds[0], fds[1] }}; +#endif + return 1; +} + +void cr_redirect(int fd_kind, s_pipe_handle *pipe, int fd_index, int noblock) { + fflush(get_std_file(fd_kind)); + if (make_redirect_pipe(pipe, noblock) < 0) cr_assert_fail("Could not redirect standard file descriptor."); cr_std_fd fd = get_std_fd(fd_kind); @@ -463,21 +527,21 @@ void cr_redirect(int fd_kind, s_pipe_handle *pipe, int fd_index) { SetStdHandle(fd, pipe->fhs[fd_index]); #else close(fd); - dup2(fd, pipe->fds[fd_index]); + dup2(pipe->fds[fd_index], fd); close(pipe->fds[fd_index]); #endif } void cr_redirect_stdout(void) { - cr_redirect(CR_STDOUT, &stdout_redir, PIPE_WRITE); + cr_redirect(CR_STDOUT, &stdout_redir, PIPE_WRITE, 1); } void cr_redirect_stderr(void) { - cr_redirect(CR_STDERR, &stderr_redir, PIPE_WRITE); + cr_redirect(CR_STDERR, &stderr_redir, PIPE_WRITE, 1); } void cr_redirect_stdin(void) { - cr_redirect(CR_STDIN, &stdin_redir, PIPE_READ); + cr_redirect(CR_STDIN, &stdin_redir, PIPE_READ, 0); } FILE* cr_get_redirected_stdout(void) { @@ -509,3 +573,27 @@ FILE* cr_get_redirected_stdin(void) { } return f; } + +int cr_file_match_str(FILE* f, const char *str) { + size_t len = strlen(str); + + fputs(str, stderr); + char buf[512]; + size_t read; + int matches = 0; + while ((read = fread(buf, 1, sizeof (buf), f)) > 0) { + matches = !strncmp(buf, str, read); + if (!matches || read > len) { + matches = 0; + break; + } + + len -= read; + str += read; + } + + // consume the rest of what's available + while (fread(buf, 1, sizeof (buf), f) > 0); + + return matches; +} diff --git a/src/process.c b/src/process.c index dd0a0c7..561665c 100644 --- a/src/process.c +++ b/src/process.c @@ -27,6 +27,7 @@ #include "criterion/types.h" #include "criterion/options.h" +#include "criterion/redirect.h" #include "process.h" #include "event.h" #include "posix-compat.h" @@ -61,7 +62,7 @@ struct event *worker_read_event(struct process *proc) { } void run_worker(struct worker_context *ctx) { - fclose(stdin); + cr_redirect_stdin(); g_event_pipe = pipe_out(ctx->pipe, 1); ctx->func(ctx->test, ctx->suite);