Added file mocking utility
This commit is contained in:
parent
a18934e37a
commit
3a2fe96653
5 changed files with 284 additions and 34 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
# define SIGPROF 27
|
||||
# define CR_EXCEPTION_TIMEOUT 0xC0001042
|
||||
# else
|
||||
# include <sys/param.h>
|
||||
# include <sys/wait.h>
|
||||
# endif
|
||||
|
||||
|
|
|
@ -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
48
test/redirect.cc
Normal 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");
|
||||
}
|
Loading…
Add table
Reference in a new issue