From 3a2fe9665399f18e27913fc8ae564b5c3b71b281 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Tue, 15 Sep 2015 19:02:13 +0200 Subject: [PATCH] Added file mocking utility --- include/criterion/redirect.h | 113 +++++++++++++++++-------- src/posix-compat.c | 154 +++++++++++++++++++++++++++++++++++ src/posix-compat.h | 1 + test/CMakeLists.txt | 2 + test/redirect.cc | 48 +++++++++++ 5 files changed, 284 insertions(+), 34 deletions(-) create mode 100644 test/redirect.cc diff --git a/include/criterion/redirect.h b/include/criterion/redirect.h index 7adaf9b..3e46997 100644 --- a/include/criterion/redirect.h +++ b/include/criterion/redirect.h @@ -33,7 +33,7 @@ # include # ifdef __GNUC__ -# include +# include # endif # else # include @@ -52,6 +52,8 @@ 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_API int cr_file_match_file(CR_STDN FILE* f, CR_STDN FILE* ref); +CR_API CR_STDN FILE *cr_mock_file_size(size_t max_size); + CR_END_C_API # define cr_assert_redir_op_(Fail, Fun, Op, File, Str, ...) \ @@ -133,15 +135,15 @@ CR_END_C_API # ifdef __cplusplus namespace criterion { - template - class basic_ofstream : public std::basic_ofstream { - public: - basic_ofstream(FILE* f) + template + class stream_mixin : public Super { +public: + stream_mixin(FILE* f) # ifdef __GNUC__ - : std::ofstream() - , fbuf(new ::__gnu_cxx::stdio_filebuf(f, std::ios::out)) + : Super() + , fbuf(new ::__gnu_cxx::stdio_sync_filebuf(f)) # else - : std::ofstream(f) + : Super(f) # endif , file(f) { @@ -150,47 +152,79 @@ namespace criterion { # endif } + stream_mixin(const stream_mixin& other) = delete; + stream_mixin& operator=(const stream_mixin& other) = delete; + + stream_mixin(stream_mixin&& other) : +# ifdef __GNUC__ + fbuf(std::move(other.fbuf)), +# endif + file(std::move(other.file)) + {} + + stream_mixin& operator=(stream_mixin&& other) { +# ifdef __GNUC__ + fbuf = std::move(other.fbuf); +# endif + file = std::move(other.file); + } + void close(void) { - std::basic_ofstream::flush(); - std::basic_ofstream::close(); + Super::flush(); + Super::close(); std::fclose(file); } private: # ifdef __GNUC__ - std::unique_ptr<::__gnu_cxx::stdio_filebuf> fbuf; + std::shared_ptr<::__gnu_cxx::stdio_sync_filebuf> fbuf; # endif std::FILE* file; }; template - class basic_ifstream : public std::basic_ifstream { + using ofstream_mixin = stream_mixin>; + + template + using ifstream_mixin = stream_mixin>; + + template + using fstream_mixin = stream_mixin>; + + template + class basic_ofstream : public ofstream_mixin { + public: + basic_ofstream(FILE* f) + : ofstream_mixin(f) + {} + + basic_ofstream(basic_ofstream&& other) + : ofstream_mixin(std::move(other)) + {} + }; + + template + class basic_ifstream : public ifstream_mixin { public: basic_ifstream(FILE* f) -# ifdef __GNUC__ - : std::ifstream() - , fbuf(new ::__gnu_cxx::stdio_filebuf(f, std::ios::in)) -# else - : std::ifstream(f) -# endif - , file(f) - { -# ifdef __GNUC__ - std::ios::rdbuf(&*fbuf); -# endif - } + : ifstream_mixin(f) + {} - void close(void) { - std::basic_ifstream::flush(); - std::basic_ifstream::close(); - std::fclose(file); - } + basic_ifstream(basic_ifstream&& other) + : ifstream_mixin(std::move(other)) + {} + }; - private: -# ifdef __GNUC__ - std::unique_ptr<::__gnu_cxx::stdio_filebuf> fbuf; -# endif - std::FILE* file; + template + class basic_fstream : public fstream_mixin { + public: + basic_fstream(FILE* f) + : fstream_mixin(f) + {} + + basic_fstream(basic_fstream&& other) + : fstream_mixin(std::move(other)) + {} }; template @@ -217,6 +251,7 @@ namespace criterion { using ofstream = basic_ofstream; using ifstream = basic_ifstream; + using fstream = basic_fstream; static inline ofstream& get_redirected_cin(void) { return get_redirected_out_stream_::call(cr_get_redirected_stdin()); @@ -229,6 +264,16 @@ namespace criterion { static inline ifstream& get_redirected_cerr(void) { return get_redirected_in_stream_::call(cr_get_redirected_stderr()); } + +# if __GNUC__ >= 5 + static inline fstream mock_file(size_t max_size) { + return fstream(cr_mock_file_size(max_size)); + } + + static inline fstream mock_file(void) { + return mock_file(4096); + } +# endif } # endif diff --git a/src/posix-compat.c b/src/posix-compat.c index 72dd6db..a8061da 100644 --- a/src/posix-compat.c +++ b/src/posix-compat.c @@ -21,7 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#define _GNU_SOURCE 1 #include +#include +#include #include "posix-compat.h" #include "process.h" #include "criterion/assert.h" @@ -581,3 +584,154 @@ FILE* cr_get_redirected_stdin(void) { } return f; } + +#if defined(BSD) || defined(__unix__) + +# ifdef BSD +typedef int cr_count; +typedef int cr_retcount; +typedef fpos_t cr_off; +# else +typedef size_t cr_count; +typedef ssize_t cr_retcount; +typedef off64_t cr_off; +# endif + +struct memfile { + size_t size; + size_t region_size; + size_t cur; + size_t max_size; + char *mem; +}; + +static inline size_t size_safe_add(size_t size, size_t cur, cr_off off) { + cur = cur < SIZE_MAX - off ? cur + off : SIZE_MAX; + return cur < size ? cur : size; +} + +static inline size_t off_safe_add(size_t size, size_t cur, cr_off off) { + if (off >= 0) + cur = cur < SIZE_MAX - off ? cur + off : SIZE_MAX; + else + cur = cur > (size_t) -off ? cur + off : 0; + return cur < size ? cur : size; +} + +# define errno_return(Errno, Val) \ + do { \ + errno = (Errno); \ + return (Val); \ + } while (0) + +static cr_retcount mock_file_read(void *cookie, char *buf, cr_count count) { + struct memfile *mf = cookie; +# ifdef BSD + if (count < 0) + errno_return(EINVAL, (cr_retcount) -1); +# endif + if (mf->cur >= mf->size || count == 0) + return 0; + + size_t end = size_safe_add(mf->size, mf->cur, count); + count = end - mf->cur; + memcpy(buf, mf->mem + mf->cur, count); + mf->cur = end; + return count; +} + +static cr_retcount mock_file_write(void *cookie, const char *buf, cr_count count) { + struct memfile *mf = cookie; +# ifdef BSD + if (count < 0) + errno_return(EINVAL, (cr_retcount) -1); +# endif + if (count == 0) + return 0; + + if (mf->cur >= mf->max_size) + errno_return(EIO, (cr_retcount) -1); + + size_t end = size_safe_add(mf->max_size, mf->cur, count); + if (mf->size < end) + mf->size = end; + count = end - mf->cur; + + if (mf->size > mf->region_size) { + while (mf->size > mf->region_size) + mf->region_size = mf->region_size * 3 / 2; + char *newptr = realloc(mf->mem, mf->region_size); + if (!newptr) + errno_return(EIO, (cr_retcount) -1); + mf->mem = newptr; + } + memcpy(mf->mem + mf->cur, buf, count); + mf->cur = end; + return count; +} + +# ifdef BSD +static cr_off mock_file_seek(void *cookie, cr_off off, int whence) { + struct memfile *mf = cookie; + switch (whence) { + case SEEK_SET: return (mf->cur = off); + case SEEK_CUR: return (mf->cur = off_safe_add(mf->size, mf->cur, off)); + case SEEK_END: return (mf->cur = off_safe_add(mf->size, mf->size, off)); + default: break; + } + errno = EINVAL; + return (off_t) -1; +} +# else +static int mock_file_seek(void *cookie, cr_off *off, int whence) { + struct memfile *mf = cookie; + switch (whence) { + case SEEK_SET: mf->cur = *off; break; + case SEEK_CUR: *off = (mf->cur = off_safe_add(mf->size, mf->cur, *off)); break; + case SEEK_END: *off = (mf->cur = off_safe_add(mf->size, mf->size, *off)); break; + default: errno = EINVAL; return -1; + } + return 0; +} +# endif + +static int mock_file_close(void *cookie) { + struct memfile *mf = cookie; + free(mf->mem); + free(cookie); + return 0; +} +#endif + +FILE *cr_mock_file_size(size_t max_size) { +#if defined(__unix__) || defined(BSD) + struct memfile *cookie = malloc(sizeof (struct memfile)); + *cookie = (struct memfile) { + .max_size = max_size, + .region_size = 4096, + .mem = malloc(4096), + }; + + FILE *f; +# ifdef __unix__ + f = fopencookie(cookie, "w+", (cookie_io_functions_t) { + .read = mock_file_read, + .write = mock_file_write, + .seek = mock_file_seek, + .close = mock_file_close, + }); +# else + f = funopen(cookie, + mock_file_read, + mock_file_write, + mock_file_seek, + mock_file_close); +# endif + return f; +#else + (void) max_size; + + // fallback to tmpfile() + return tmpfile(); +#endif +} diff --git a/src/posix-compat.h b/src/posix-compat.h index e6b3005..a0c86e7 100644 --- a/src/posix-compat.h +++ b/src/posix-compat.h @@ -47,6 +47,7 @@ # define SIGPROF 27 # define CR_EXCEPTION_TIMEOUT 0xC0001042 # else +# include # include # endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d9122f2..978ba2a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,11 +1,13 @@ if (NOT MSVC) set(CMAKE_C_FLAGS "-std=gnu99 -Wall -Wextra") + set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wextra") endif () include_directories(../include ../src) set(TEST_SOURCES ordered-set.c + redirect.cc ) add_executable(criterion_unit_tests EXCLUDE_FROM_ALL ${TEST_SOURCES}) diff --git a/test/redirect.cc b/test/redirect.cc new file mode 100644 index 0000000..0563ea9 --- /dev/null +++ b/test/redirect.cc @@ -0,0 +1,48 @@ +#include +#include +#include + +// set a timeout for I/O tests +TestSuite(redirect, .timeout = 0.1); + +Test(redirect, mock) { + auto fmock = criterion::mock_file(); + + fmock << "Hello" << std::flush; + fmock.seekg(0); + + std::string contents; + fmock >> contents; + + cr_assert_eq(contents, "Hello"); +} + +Test(redirect, stdout_) { + cr_redirect_stdout(); + + std::cout << "Foo" << std::flush; + + cr_expect_stdout_eq_str("Foo"); + cr_expect_stdout_neq_str("Bar"); +} + +Test(redirect, stderr_) { + cr_redirect_stderr(); + + std::cerr << "Foo" << std::flush; + + cr_expect_stderr_eq_str("Foo"); + cr_expect_stderr_neq_str("Bar"); +} + +Test(redirect, stdin_) { + auto& f_cin = criterion::get_redirected_cin(); + f_cin << "Foo"; + f_cin.close(); + + std::string read; + std::cin >> read; + + cr_expect_eq(read, "Foo"); + cr_expect_neq(read, "Bar"); +}