Added file mocking utility

This commit is contained in:
Snaipe 2015-09-15 19:02:13 +02:00
parent a18934e37a
commit 3a2fe96653
5 changed files with 284 additions and 34 deletions

View file

@ -33,7 +33,7 @@
# include <fstream>
# ifdef __GNUC__
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# endif
# else
# include <stdio.h>
@ -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 <typename CharT>
class basic_ofstream : public std::basic_ofstream<CharT> {
public:
basic_ofstream(FILE* f)
template <typename CharT, typename Super>
class stream_mixin : public Super {
public:
stream_mixin(FILE* f)
# ifdef __GNUC__
: std::ofstream()
, fbuf(new ::__gnu_cxx::stdio_filebuf<CharT>(f, std::ios::out))
: Super()
, fbuf(new ::__gnu_cxx::stdio_sync_filebuf<CharT>(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<CharT>::flush();
std::basic_ofstream<CharT>::close();
Super::flush();
Super::close();
std::fclose(file);
}
private:
# ifdef __GNUC__
std::unique_ptr<::__gnu_cxx::stdio_filebuf<CharT>> fbuf;
std::shared_ptr<::__gnu_cxx::stdio_sync_filebuf<CharT>> fbuf;
# endif
std::FILE* file;
};
template <typename CharT>
class basic_ifstream : public std::basic_ifstream<CharT> {
using ofstream_mixin = stream_mixin<CharT, std::basic_ofstream<CharT>>;
template <typename CharT>
using ifstream_mixin = stream_mixin<CharT, std::basic_ifstream<CharT>>;
template <typename CharT>
using fstream_mixin = stream_mixin<CharT, std::basic_fstream<CharT>>;
template <typename CharT>
class basic_ofstream : public ofstream_mixin<CharT> {
public:
basic_ofstream(FILE* f)
: ofstream_mixin<CharT>(f)
{}
basic_ofstream(basic_ofstream&& other)
: ofstream_mixin<CharT>(std::move(other))
{}
};
template <typename CharT>
class basic_ifstream : public ifstream_mixin<CharT> {
public:
basic_ifstream(FILE* f)
# ifdef __GNUC__
: std::ifstream()
, fbuf(new ::__gnu_cxx::stdio_filebuf<CharT>(f, std::ios::in))
# else
: std::ifstream(f)
# endif
, file(f)
{
# ifdef __GNUC__
std::ios::rdbuf(&*fbuf);
# endif
}
: ifstream_mixin<CharT>(f)
{}
void close(void) {
std::basic_ifstream<CharT>::flush();
std::basic_ifstream<CharT>::close();
std::fclose(file);
}
basic_ifstream(basic_ifstream&& other)
: ifstream_mixin<CharT>(std::move(other))
{}
};
private:
# ifdef __GNUC__
std::unique_ptr<::__gnu_cxx::stdio_filebuf<CharT>> fbuf;
# endif
std::FILE* file;
template <typename CharT>
class basic_fstream : public fstream_mixin<CharT> {
public:
basic_fstream(FILE* f)
: fstream_mixin<CharT>(f)
{}
basic_fstream(basic_fstream&& other)
: fstream_mixin<CharT>(std::move(other))
{}
};
template <typename CharT>
@ -217,6 +251,7 @@ namespace criterion {
using ofstream = basic_ofstream<char>;
using ifstream = basic_ifstream<char>;
using fstream = basic_fstream<char>;
static inline ofstream& get_redirected_cin(void) {
return get_redirected_out_stream_<char>::call(cr_get_redirected_stdin());
@ -229,6 +264,16 @@ namespace criterion {
static inline ifstream& get_redirected_cerr(void) {
return get_redirected_in_stream_<char>::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

View file

@ -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 <assert.h>
#include <errno.h>
#include <stdint.h>
#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
}

View file

@ -47,6 +47,7 @@
# define SIGPROF 27
# define CR_EXCEPTION_TIMEOUT 0xC0001042
# else
# include <sys/param.h>
# include <sys/wait.h>
# endif

View file

@ -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})

48
test/redirect.cc Normal file
View file

@ -0,0 +1,48 @@
#include <criterion/criterion.h>
#include <criterion/redirect.h>
#include <iostream>
// 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");
}