[v2.1.0] Merge branch 'bleeding' (Version release)

This commit is contained in:
Snaipe 2015-09-21 22:30:36 +02:00
commit 3148348c37
75 changed files with 2608 additions and 932 deletions

View file

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.0.2
current_version = 2.1.0
commit = False
[bumpversion:file:CMakeLists.txt]

26
.cmake/Modules/Gcov.cmake Normal file
View file

@ -0,0 +1,26 @@
if (NOT DEFINED ENV{GCOV})
find_program(GCOV_EXECUTABLE gcov)
else()
find_program(GCOV_EXECUTABLE $ENV{GCOV})
endif()
#file(GLOB_RECURSE GCNO_FILES "${CMAKE_CURRENT_BINARY_DIR}/*.gcno")
if (WIN32)
file(GLOB_RECURSE GCDA_FILES "${COV_PATH}\\*.gcda")
else ()
file(GLOB_RECURSE GCDA_FILES "${COV_PATH}/*.gcda")
endif ()
#execute_process(
# COMMAND ${GCOV_EXECUTABLE} -lcp ${GCNO_FILES}
# WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
#)
foreach (GCDA ${GCDA_FILES})
get_filename_component(DIR ${GCDA} PATH)
execute_process(
COMMAND ${GCOV_EXECUTABLE} -lcp -o ${DIR} ${GCDA}
WORKING_DIRECTORY ${COV_PATH}
)
endforeach ()

View file

@ -15,31 +15,39 @@ addons:
packages:
- gcc-4.9
- g++-4.9
before_install:
- export GCOV="gcov-4.9"
- export LOCAL_INSTALL="$HOME"
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/lib
- export CFLAGS="-g -O0"
- export CXX="g++-4.9"
script:
- mkdir -p build
- cd build
- cmake -DCOVERALLS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=$HOME -DCMAKE_INSTALL_PREFIX=criterion-${TRAVIS_TAG} ..
- make
- make criterion_tests
- make test
after_success:
- make coveralls
after_failure:
- cat Testing/Temporary/LastTest.log samples/*.{out,err} ../samples/tests/*.{out,err}
env:
global:
secure: bzZcWjdqoTgceC40kEBucx7NuWYJPk+rxgF3UJJDXi+ijQAFYPv70p5eVsGR6rfc+XgqXCxcUFQtuL4ZVt7QEfVk1ZOJITNeHbKIeKaEYS4nX8mFf+CBeEm9bJGZ04KiQJdJu5mzzAHvXbW7roGXDGWe1Bjnk5wwA+dNUCa7H04=
GCOV: gcov-4.9
CXX: g++-4.9
matrix:
- CONFIGURATION=Debug COVERAGE=ON
- CONFIGURATION=Release COVERAGE=OFF
- CONFIGURATION=RelWithDebInfo COVERAGE=OFF
script:
- mkdir -p build
- cd build
- >
cmake
-Wno-dev
-DCOVERALLS=${COVERAGE}
-DCMAKE_BUILD_TYPE=${CONFIGURATION}
-DCMAKE_INSTALL_PREFIX=criterion-${TRAVIS_TAG}
..
- make
- make criterion_tests
- make test
after_success:
- make gcov
- bash <(curl -s https://codecov.io/bash)
after_failure:
- cat Testing/Temporary/LastTest.log samples/*.{out,err} ../samples/tests/*.{out,err}
before_deploy:
- make install
- tar -cvjf criterion-${TRAVIS_TAG}-${TRAVIS_OS_NAME}-x86_64.tar.bz2 criterion-${TRAVIS_TAG}
- make install
- tar -cvjf criterion-${TRAVIS_TAG}-${TRAVIS_OS_NAME}-x86_64.tar.bz2 criterion-${TRAVIS_TAG}
deploy:
provider: releases
@ -50,3 +58,4 @@ deploy:
on:
repo: Snaipe/Criterion
tags: true
condition: $CONFIGURATION = RelWithDebInfo

View file

@ -17,6 +17,7 @@ add_subdirectory(dependencies/libcsptr/ EXCLUDE_FROM_ALL)
add_subdirectory(dependencies/dyncall/ EXCLUDE_FROM_ALL)
include_directories(
/usr/local/include/
dependencies/libcsptr/include/
dependencies/dyncall/dyncall/
)
@ -28,7 +29,7 @@ endif ()
# Project setup & environment variables
set(PROJECT_VERSION "2.0.2")
set(PROJECT_VERSION "2.1.0")
set(LOCALEDIR ${CMAKE_INSTALL_PREFIX}/share/locale)
set(GettextTranslate_ALL 1)
set(GettextTranslate_GMO_BINARY 1)
@ -75,39 +76,52 @@ find_package(PCRE)
# List sources and headers
set(SOURCE_FILES
src/abort.c
src/abort.h
src/event.c
src/event.h
src/report.c
src/report.h
src/runner.c
src/runner.h
src/process.c
src/process.h
src/stats.c
src/stats.h
src/core/abort.c
src/core/abort.h
src/core/report.c
src/core/report.h
src/core/runner.c
src/core/runner.h
src/core/worker.c
src/core/worker.h
src/core/stats.c
src/core/stats.h
src/core/ordered-set.c
src/core/theories.c
src/compat/internal.h
src/compat/pipe.c
src/compat/pipe.h
src/compat/section.c
src/compat/section.h
src/compat/process.c
src/compat/process.h
src/compat/basename.c
src/compat/basename.h
src/compat/mockfile.c
src/compat/time.c
src/compat/time.h
src/compat/posix.h
src/compat/alloc.c
src/compat/alloc.h
src/io/redirect.c
src/io/event.c
src/io/event.h
src/io/asprintf.c
src/io/file.c
src/log/logging.c
src/log/tap.c
src/log/normal.c
src/options.c
src/timer.c
src/timer.h
src/i18n.c
src/i18n.h
src/ordered-set.c
src/posix-compat.c
src/theories.c
src/asprintf.c
src/file.c
src/main.c
src/entry.c
src/string/i18n.c
src/string/i18n.h
src/entry/options.c
src/entry/main.c
src/entry/entry.c
)
if (PCRE_FOUND)
set (SOURCE_FILES ${SOURCE_FILES}
src/extmatch.c
src/extmatch.h
src/string/extmatch.c
src/string/extmatch.h
)
set(HAVE_PCRE 1)
endif ()
@ -128,6 +142,7 @@ set(INTERFACE_FILES
include/criterion/asprintf-compat.h
include/criterion/designated-initializer-compat.h
include/criterion/preprocess.h
include/criterion/alloc.h
)
# Generate the configure file
@ -170,6 +185,13 @@ install(TARGETS criterion
add_custom_target(criterion_tests)
add_custom_target(gcov
"${CMAKE_COMMAND}"
-DSOURCE_FILES="${SOURCE_FILES}"
-DCOV_PATH="${CMAKE_CURRENT_BINARY_DIR}"
-P "${CMAKE_MODULE_PATH}/Gcov.cmake"
)
enable_testing()
add_subdirectory(samples)
add_subdirectory(test)

View file

@ -33,9 +33,15 @@ be merged.
|- .cmake/: CMake modules
|- dependencies/: dependencies for building libcriterion
|- doc/: Sphinx documentation files
|- dev/: Developer files
|- include/criterion/: Public API
|- src/: Sources for libcriterion
| `- log/: Output providers, all the output logic in general
| |- compat/: Cross-platform abstractions for platform-dependent code
| |- core/: Core mechanisms used to run the tests
| |- entry/: Entry-point related sources, and default main function
| |- io/: IO related functions, redirections
| |- log/: Output providers, all the output logic in general
| `- string/: String manipulation functions, i18n
|- po/: Translation files, i18n stuff
|- test/: Unit tests for libcriterion
`- samples/: Sample files

View file

@ -1,3 +1,9 @@
2015-09-21 Franklin "Snaipe" Mathieu <franklinmathieu@gmail.com>
* criterion: version 2.1.0
* Addition: Added file mocking utilities
* Addition: Added parameterized tests
2015-09-20 Franklin "Snaipe" Mathieu <franklinmathieu@gmail.com>
* criterion: version 2.0.2

View file

@ -4,7 +4,7 @@
[![Unix Build Status](https://travis-ci.org/Snaipe/Criterion.svg?branch=bleeding)](https://travis-ci.org/Snaipe/Criterion)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/Snaipe/Criterion?svg=true&branch=bleeding)](https://ci.appveyor.com/project/Snaipe/Criterion/branch/bleeding)
[![Coverage Status](https://coveralls.io/repos/Snaipe/Criterion/badge.svg?branch=bleeding)](https://coveralls.io/r/Snaipe/Criterion?branch=bleeding)
[![Coverage Status](https://img.shields.io/codecov/c/github/Snaipe/Criterion/bleeding.svg)](https://codecov.io/github/Snaipe/Criterion?branch=bleeding)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/Snaipe/Criterion/blob/master/LICENSE)
[![Version](https://img.shields.io/github/tag/Snaipe/Criterion.svg?label=version&style=flat)](https://github.com/Snaipe/Criterion/releases)
@ -32,16 +32,17 @@ the user would have with other frameworks:
* [x] Test are isolated in their own process, crashes and signals can be
reported and tested.
* [x] Unified interface between C and C++: include the criterion header and it *just* works.
* [x] There is a support for theories alongside tests
* [x] Supports parameterized tests and theories.
* [x] Progress and statistics can be followed in real time with report hooks.
* [x] TAP output format can be enabled with an option.
* [x] Runs on Linux, FreeBSD, Mac OS X, and Windows (Compiling with MinGW GCC and Visual Studio 2015+).
## Downloads
* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v2.0.2/criterion-v2.0.2-linux-x86_64.tar.bz2)
* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v2.0.2/criterion-v2.0.2-osx-x86_64.tar.bz2)
* [Windows (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v2.0.2/criterion-v2.0.2-windows-x86_64.tar.bz2)
* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v2.1.0/criterion-v2.1.0-linux-x86_64.tar.bz2)
* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v2.1.0/criterion-v2.1.0-osx-x86_64.tar.bz2)
* [Windows (MSVC - x86_64)](https://github.com/Snaipe/Criterion/releases/download/v2.1.0/criterion-v2.1.0-windows-msvc-x86_64.tar.bz2)
* [Windows (MinGW - x86_64)](https://github.com/Snaipe/Criterion/releases/download/v2.1.0/criterion-v2.1.0-windows-mingw-x86_64.tar.bz2)
If you have a different platform, you can still [build the library from source](http://criterion.readthedocs.org/en/latest/setup.html#installation)

View file

@ -1,4 +1,4 @@
version: 2.0.2_b{build}-{branch}
version: 2.1.0_b{build}-{branch}
os: Visual Studio 2015
@ -11,7 +11,6 @@ environment:
secure: 5nuCg+faxFPeppoNNcSwVobswAVFUf8ut83vw8CX/4W2y0kZkGmwEfCUxSQWiQDU
CI_NAME: appveyor
CI_JOB_ID: $(APPVEYOR_JOB_ID)
LOCAL_INSTALL: $(APPVEYOR_BUILD_FOLDER)
GCOV_PREFIX: $(APPVEYOR_BUILD_FOLDER)
matrix:
- COMPILER: mingw
@ -22,9 +21,13 @@ environment:
clone_depth: 5
platform:
- x86
- x86_64
configuration: Release
configuration:
- Debug
- Release
- RelWithDebInfo
install:
- ps: $env:RELEASE_NAME = $env:APPVEYOR_REPO_BRANCH -replace "/", "-"
@ -36,7 +39,7 @@ install:
cmake
-Wno-dev
-DCMAKE_INSTALL_PREFIX="criterion-%RELEASE_NAME%"
-DCMAKE_PREFIX_PATH="%LOCAL_INSTALL%"
-DCMAKE_BUILD_TYPE="%CONFIGURATION%"
-G "%GENERATOR%"
..
@ -73,3 +76,4 @@ deploy:
prerelease: false
on:
appveyor_repo_tag: true
configuration: RelWithDebInfo

View file

@ -39,7 +39,7 @@ copyright = u'2015, Franklin "Snaipe" Mathieu'
# built documents.
#
# The short X.Y version.
version = '2.0.2'
version = '2.1.0'
# The full version, including alpha/beta/rc tags.
release = version

View file

@ -41,10 +41,7 @@ The flow of the test process goes as follows:
Hook Parameters
---------------
A report hook may take zero or one parameter. If a parameter is given, it
is undefined behaviour if it is not a pointer type and not of the proper pointed
type for that phase.
A report hook takes exactly one parameter.
Valid types for each phases are:
* ``struct criterion_test_set *`` for ``PRE_ALL``.
@ -56,17 +53,13 @@ Valid types for each phases are:
* ``struct criterion_suite_stats *`` for ``POST_SUITE``.
* ``struct criterion_global_stats *`` for ``POST_ALL``.
For instance, these are valid report hook declarations for the ``PRE_TEST`` phase:
For instance, this is a valid report hook declaration for the ``PRE_TEST`` phase:
.. code-block:: c
#include <criterion/criterion.h>
#include <criterion/hooks.h>
ReportHook(PRE_TEST)() {
// not using the parameter
}
ReportHook(PRE_TEST)(struct criterion_test *test) {
// using the parameter
}

View file

@ -10,6 +10,7 @@ Criterion
assert
hooks
env
parameterized
theories
internal
faq

140
doc/parameterized.rst Normal file
View file

@ -0,0 +1,140 @@
Using parameterized tests
=========================
Parameterized tests are useful to repeat a specific test logic over a finite
set of parameters.
Due to limitations on how generated parameters are passed, parameterized tests
can only accept one pointer parameter; however, this is not that much of a
problem since you can just pass a structure containing the context you need.
Adding parameterized tests
--------------------------
Adding parameterized tests is done by defining the parameterized test function,
and the parameter generator function:
.. code-block:: c
#include <criterion/parameterized.h>
ParameterizedTestParameter(suite_name, test_name) = {
void *params;
size_t nb_params;
// generate parameter set
return cr_make_param_array(Type, params, nb_params);
}
ParameterizedTest(Type *param, suite_name, test_name) {
// contents of the test
}
``suite_name`` and ``test_name`` are the identifiers of the test suite and
the test, respectively. These identifiers must follow the language
identifier format.
``Type`` is the compound type of the generated array. ``params`` and ``nb_params``
are the pointer and the length of the generated array, respectively.
Passing multiple parameters
---------------------------
As said earlier, parameterized tests only take one parameter, so passing
multiple parameters is, in the strict sense, not possible. However, one can
easily use a struct to hold the context as a workaround:
.. code-block:: c
#include <criterion/parameterized.h>
struct my_params {
int param0;
double param1;
...
};
ParameterizedTestParameter(suite_name, test_name) = {
size_t nb_params = 32;
struct my_params *params = cr_malloc(sizeof (struct my_params) * nb_params);
// generate parameter set
params[0] = ...
params[1] = ...
...
return cr_make_param_array(struct my_params, params, nb_params);
}
ParameterizedTest(struct my_params *param, suite_name, test_name) {
// access param.param0, param.param1, ...
}
Dynamically allocating fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Any dynamic memory allocation done from a ParameterizedTestParameter function
**must** be done with ``cr_malloc``, ``cr_calloc``, or ``cr_realloc``.
Any pointer returned by those 3 functions must be passed to ``cr_free`` after
you have no more use of it.
It is undefined behaviour to use any other allocation function (such as ``malloc``)
from the scope of a ParameterizedTestParameter function.
In C++, these methods should not be called explicitely -- instead, you should
use:
* ``criterion::new_obj<Type>(params...)`` to allocate an object of type ``Type``
and call its constructor taking ``params...``.
The function possess the exact same semantics as ``new Type(params...)``.
* ``criterion::delete_obj(obj)`` to destroy an object previously allocated by
``criterion::new_obj``.
The function possess the exact same semantics as ``delete obj``.
* ``criterion::new_arr<Type>(size)`` to allocate an array of objects of type ``Type``
and length ``size``. ``Type`` is initialized by calling its default constructor.
The function possess the exact same semantics as ``new Type[size]``.
* ``criterion::delete_arr(array)`` to destroy an array previously allocated by
``criterion::new_arr``.
The function possess the exact same semantics as ``delete[] array``.
Freeing dynamically allocated parameter fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One can pass an extra parameter to ``cr_make_param_array`` to specify
the cleanup function that should be called on the generated parameter context:
.. code-block:: c
#include <criterion/parameterized.h>
struct my_params {
int *some_int_ptr;
};
void cleanup_params(struct criterion_test_params *ctp) {
cr_free(((struct my_params *) ctp->params)->some_int_ptr);
}
ParameterizedTestParameter(suite_name, test_name) = {
static my_params param = {
.some_int_ptr = cr_malloc(sizeof (int));
};
*param.some_int_ptr = 42;
return cr_make_param_array(struct my_params, &param, 1, cleanup_params);
}
Configuring parameterized tests
-------------------------------
Parameterized tests can optionally recieve configuration parameters to alter
their own behaviour, and are applied to each iteration of the parameterized
test individually (this means that the initialization and finalization runs once
per iteration).
Those parameters are the same ones as the ones of the ``Test`` macro function
(c.f. :ref:`test-config-ref`).

View file

@ -239,4 +239,27 @@ crash is the divison of INT_MAX by -1, which is undefined.
Fixing this is as easy as changing the prototypes of ``my_mul`` and ``my_div``
to operate on ``long long`` rather than ``int``.
What's the difference between theories and parameterized tests ?
----------------------------------------------------------------
While it may at first seem that theories and parameterized tests are the same,
just because they happen to take multiple parameters does not mean that they
logically behave in the same manner.
Parameterized tests are useful to test a specific logic against a fixed, *finite*
set of examples that you need to work.
Theories are, well, just that: theories. They represent a test against an
universal truth, regardless of the input data matching its predicates.
Implementation-wise, Criterion also marks the separation by the way that both
are executed:
Each parameterized test iteration is run in its own test; this means that
one parameterized test acts as a collection of many tests, and gets reported
as such.
On the other hand, a theory act as one single test, since the size and contents
of the generated data set is not relevant. It does not make sense to say that
an universal truth is "partially true", so if one of the iteration fails, then
the whole test fails.

100
include/criterion/alloc.h Normal file
View file

@ -0,0 +1,100 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef CRITERION_ALLOC_H_
# define CRITERION_ALLOC_H_
# include <stddef.h>
# include "common.h"
CR_BEGIN_C_API
CR_API void *cr_malloc(size_t size);
CR_API void *cr_calloc(size_t nmemb, size_t size);
CR_API void *cr_realloc(void *ptr, size_t size);
CR_API void cr_free(void *ptr);
CR_END_C_API
# ifdef __cplusplus
# include <type_traits>
namespace criterion {
void *(*const malloc)(size_t) = cr_malloc;
void (*const free)(void *) = cr_free;
void *(*const calloc)(size_t, size_t) = cr_calloc;
void *(*const realloc)(void *, size_t) = cr_realloc;
template<typename T, typename... Params>
T* new_obj(Params... params) {
T* obj = static_cast<T*>(cr_malloc(sizeof (T)));
new (obj) T(params...);
return obj;
}
template<typename T>
typename std::enable_if<std::is_fundamental<T>::value>::type*
new_arr(size_t len) {
void *ptr = cr_malloc(sizeof (size_t) + sizeof (T) * len);
*(reinterpret_cast<size_t*>(ptr)) = len;
T* arr = reinterpret_cast<T*>(reinterpret_cast<size_t*>(ptr) + 1);
return arr;
}
template<typename T>
T* new_arr(size_t len) {
void *ptr = cr_malloc(sizeof (size_t) + sizeof (T) * len);
*(reinterpret_cast<size_t*>(ptr)) = len;
T* arr = reinterpret_cast<T*>(reinterpret_cast<size_t*>(ptr) + 1);
for (size_t i = 0; i < len; ++i)
new (arr + i) T();
return arr;
}
template<typename T>
void delete_obj(T* ptr) {
ptr->~T();
cr_free(ptr);
}
template<typename T>
void delete_arr(typename std::enable_if<std::is_fundamental<T>::value>::type* ptr) {
cr_free(ptr);
}
template<typename T>
void delete_arr(T* ptr) {
size_t len = *(reinterpret_cast<size_t*>(ptr));
T* arr = reinterpret_cast<T*>(reinterpret_cast<size_t*>(ptr) + 1);
for (int i = 0; i < len; ++i) {
arr[i].~T();
}
cr_free(ptr);
}
}
# endif
#endif /* !CRITERION_ALLOC_H_ */

View file

@ -28,6 +28,7 @@
# include "common.h"
# include "types.h"
# include "assert.h"
# include "alloc.h"
# define IDENTIFIER_(Category, Name, Suffix) \
Category ## _ ## Name ## _ ## Suffix
@ -48,6 +49,8 @@
TEST_PROTOTYPE_(Category, Name); \
struct criterion_test_extra_data IDENTIFIER_(Category, Name, extra) = \
CR_EXPAND(CRITERION_MAKE_STRUCT(struct criterion_test_extra_data, \
.kind_ = CR_TEST_NORMAL, \
.param_ = (struct criterion_test_params(*)(void)) NULL, \
.identifier_ = #Category "/" #Name, \
.file_ = __FILE__, \
.line_ = __LINE__, \

View file

@ -112,7 +112,7 @@
CR_EXPAND(CRITERION_APPLY(CRITERION_ADD_PREFIX_ONCE, __VA_ARGS__))
# ifdef __cplusplus
# define CRITERION_MAKE_STRUCT(Type, ...) [&]() { \
# define CRITERION_MAKE_STRUCT(Type, ...) []() { \
Type t; \
std::memset(&t, 0, sizeof (t)); \
CR_EXPAND(CRITERION_ADD_PREFIX(t, __VA_ARGS__)) \

View file

@ -0,0 +1,50 @@
#ifndef CRITERION_PARAMETERIZED_H_
# define CRITERION_PARAMETERIZED_H_
# include "criterion.h"
# ifdef __cplusplus
# define CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name) \
extern "C" void IDENTIFIER_(Category, Name, impl)(Param)
# else
# define CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name) \
void IDENTIFIER_(Category, Name, impl)(Param)
# endif
# define ParameterizedTest(...) \
CR_EXPAND(ParameterizedTest_(__VA_ARGS__, .sentinel_ = 0))
# define ParameterizedTest_(Param, Category, Name, ...) \
CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name); \
struct criterion_test_extra_data IDENTIFIER_(Category, Name, extra) = \
CR_EXPAND(CRITERION_MAKE_STRUCT(struct criterion_test_extra_data, \
.kind_ = CR_TEST_PARAMETERIZED, \
.param_ = IDENTIFIER_(Category, Name, param), \
.identifier_ = #Category "/" #Name, \
.file_ = __FILE__, \
.line_ = __LINE__, \
__VA_ARGS__ \
)); \
struct criterion_test IDENTIFIER_(Category, Name, meta) = { \
#Name, \
#Category, \
(void(*)(void)) IDENTIFIER_(Category, Name, impl), \
&IDENTIFIER_(Category, Name, extra) \
}; \
SECTION_("cr_tst") \
struct criterion_test *IDENTIFIER_(Category, Name, ptr) \
= &IDENTIFIER_(Category, Name, meta) SECTION_SUFFIX_; \
CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name)
# define ParameterizedTestParameters(Category, Name) \
static struct criterion_test_params IDENTIFIER_(Category, Name, param)(void)
# ifdef __cplusplus
# define cr_make_param_array(Type, Array, ...) \
criterion_test_params(sizeof (Type), (Array), __VA_ARGS__)
# else
# define cr_make_param_array(Type, Array, ...) \
(struct criterion_test_params) { .size = sizeof (Type), (void*)(Array), __VA_ARGS__ }
# endif
#endif /* !CRITERION_PARAMETERIZED_H_ */

View file

@ -33,7 +33,15 @@
# include <fstream>
# ifdef __GNUC__
# include <ext/stdio_filebuf.h>
# if defined(__MINGW32__) || defined(__MINGW64__)
# define off_t _off_t
# define off64_t _off64_t
# endif
# include <ext/stdio_sync_filebuf.h>
# if defined(__MINGW32__) || defined(__MINGW64__)
# undef off_t
# undef off64_t
# endif
# endif
# else
# include <stdio.h>
@ -52,6 +60,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,102 +143,143 @@ CR_END_C_API
# ifdef __cplusplus
namespace criterion {
template <typename CharT, typename Super>
class stream_mixin : public Super {
public:
stream_mixin(FILE* f)
# ifdef __GNUC__
: Super()
, fbuf(new ::__gnu_cxx::stdio_sync_filebuf<CharT>(f))
# else
: Super(f)
# endif
, file(f)
{
# ifdef __GNUC__
std::ios::rdbuf(&*fbuf);
# 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) {
Super::flush();
Super::close();
std::fclose(file);
}
private:
# ifdef __GNUC__
std::shared_ptr<::__gnu_cxx::stdio_sync_filebuf<CharT>> fbuf;
# endif
std::FILE* file;
};
template <typename CharT>
class basic_ofstream : public std::basic_ofstream<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)
# ifdef __GNUC__
: std::ofstream()
, fbuf(new ::__gnu_cxx::stdio_filebuf<CharT>(f, std::ios::out))
# else
: std::ofstream(f)
# endif
, file(f)
{
# ifdef __GNUC__
std::ios::rdbuf(&*fbuf);
# endif
}
: ofstream_mixin<CharT>(f)
{}
void close(void) {
std::basic_ofstream<CharT>::flush();
std::basic_ofstream<CharT>::close();
std::fclose(file);
}
private:
# ifdef __GNUC__
std::unique_ptr<::__gnu_cxx::stdio_filebuf<CharT>> fbuf;
# endif
std::FILE* file;
basic_ofstream(basic_ofstream&& other)
: ofstream_mixin<CharT>(std::move(other))
{}
};
template <typename CharT>
class basic_ifstream : public std::basic_ifstream<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);
}
private:
# ifdef __GNUC__
std::unique_ptr<::__gnu_cxx::stdio_filebuf<CharT>> fbuf;
# endif
std::FILE* file;
basic_ifstream(basic_ifstream&& other)
: ifstream_mixin<CharT>(std::move(other))
{}
};
template <typename CharT>
struct get_redirected_out_stream_ {
static inline basic_ofstream<CharT>& call(std::FILE* f) {
static std::unique_ptr<basic_ofstream<CharT>> stream;
class basic_fstream : public fstream_mixin<CharT> {
public:
basic_fstream(FILE* f)
: fstream_mixin<CharT>(f)
{}
if (!stream)
stream.reset(new basic_ofstream<CharT>(f));
return *stream;
}
};
template <typename CharT>
struct get_redirected_in_stream_ {
static inline basic_ifstream<CharT>& call(std::FILE* f) {
static std::unique_ptr<basic_ifstream<CharT>> stream;
if (!stream)
stream.reset(new basic_ifstream<CharT>(f));
return *stream;
}
basic_fstream(basic_fstream&& other)
: fstream_mixin<CharT>(std::move(other))
{}
};
using ofstream = basic_ofstream<char>;
using ifstream = basic_ifstream<char>;
using fstream = basic_fstream<char>;
struct get_redirected_out_stream_ {
static inline ofstream& call(std::FILE* f) {
static std::unique_ptr<ofstream> stream;
if (!stream)
stream.reset(new ofstream(f));
return *stream;
}
};
struct get_redirected_in_stream_ {
static inline ifstream& call(std::FILE* f) {
static std::unique_ptr<ifstream> stream;
if (!stream)
stream.reset(new ifstream(f));
return *stream;
}
};
static inline ofstream& get_redirected_cin(void) {
return get_redirected_out_stream_<char>::call(cr_get_redirected_stdin());
return get_redirected_out_stream_::call(cr_get_redirected_stdin());
}
static inline ifstream& get_redirected_cout(void) {
return get_redirected_in_stream_<char>::call(cr_get_redirected_stdout());
return get_redirected_in_stream_::call(cr_get_redirected_stdout());
}
static inline ifstream& get_redirected_cerr(void) {
return get_redirected_in_stream_<char>::call(cr_get_redirected_stderr());
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

View file

@ -33,8 +33,39 @@ using std::size_t;
# endif
# include "common.h"
enum criterion_test_kind {
CR_TEST_NORMAL,
CR_TEST_PARAMETERIZED,
};
struct criterion_test_params {
size_t size;
void *params;
size_t length;
void (*cleanup)(struct criterion_test_params *);
# ifdef __cplusplus
constexpr criterion_test_params(size_t size, void *params, size_t length)
: size(size)
, params(params)
, length(length)
, cleanup(nullptr)
{}
constexpr criterion_test_params(size_t size, void *params, size_t length,
void (*cleanup)(struct criterion_test_params *))
: size(size)
, params(params)
, length(length)
, cleanup(cleanup)
{}
# endif
};
struct criterion_test_extra_data {
int sentinel_;
enum criterion_test_kind kind_;
struct criterion_test_params (*param_)(void);
const char *identifier_;
const char *file_;
unsigned line_;
@ -44,7 +75,7 @@ struct criterion_test_extra_data {
int exit_code;
bool disabled;
const char *description;
float timeout;
double timeout;
void *data;
};

View file

@ -1,3 +1,3 @@
# List of source files which contain translatable strings.
src/log/normal.c
src/i18n.c
src/string/i18n.c

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: criterion 2.0.0\n"
"Report-Msgid-Bugs-To: franklinmathieu+criterion@gmail.com\n"
"POT-Creation-Date: 2015-09-14 04:12+0200\n"
"POT-Creation-Date: 2015-09-16 21:18+0200\n"
"PO-Revision-Date: 2015-04-03 17:58+0200\n"
"Last-Translator: <franklinmathieu@gmail.com>\n"
"Language-Team: French\n"
@ -109,68 +109,68 @@ msgstr ""
"%1$sSynthèse: Testés: %2$s%3$lu%4$s | Validés: %5$s%6$lu%7$s | Échoués: %8$s"
"%9$lu%10$s | Plantages: %11$s%12$lu%13$s %14$s\n"
#: src/i18n.c:13
#: src/string/i18n.c:13
msgid "The conditions for this assertion were not met."
msgstr "Les conditions de cette assertion n'ont pas été remplies."
#: src/i18n.c:14
#: src/string/i18n.c:14
#, c-format
msgid "The expression %s is false."
msgstr "L'expression %s est fausse."
#: src/i18n.c:15
#: src/string/i18n.c:15
#, c-format
msgid "The expression (as strings) %s is false."
msgstr "L'expression (en tant que chaînes de caractères) %s est fausse."
#: src/i18n.c:16
#: src/string/i18n.c:16
#, c-format
msgid "%s is null."
msgstr "%s est nul."
#: src/i18n.c:17
#: src/string/i18n.c:17
#, c-format
msgid "%s is not null."
msgstr "%s n'est pas nul."
#: src/i18n.c:18
#: src/string/i18n.c:18
#, c-format
msgid "%s is empty."
msgstr "%s est vide."
#: src/i18n.c:19
#: src/string/i18n.c:19
#, c-format
msgid "%s is not empty."
msgstr "%s n'est pas vide."
#: src/i18n.c:20
#: src/string/i18n.c:20
#, fuzzy, c-format
msgid "The statement `%s` did not throw any exception."
msgstr "L'instruction `%s` n'a pas levé d'exception."
#: src/i18n.c:21
#: src/string/i18n.c:21
#, fuzzy, c-format
msgid "The statement `%s` threw some exception."
msgstr "L'instruction `%1$s` a levé une exception."
#: src/i18n.c:24
#: src/string/i18n.c:24
#, c-format
msgid "The file contents of %1$s does not match the string \"%2$s\"."
msgstr ""
"Le contenu du fichier %1$s ne correspond pas à la chaine de caractères \"%2$s"
"\"."
#: src/i18n.c:25
#: src/string/i18n.c:25
#, fuzzy, c-format
msgid "The file contents of %1$s does not match the contents of %2$s."
msgstr "Le contenu du fichier %1$s ne correspond pas au contenu de %2$s."
#: src/i18n.c:26
#: src/string/i18n.c:26
#, c-format
msgid "The statement `%1$s` did throw an instance of the `%2$s` exception."
msgstr "L'instruction `%1$s` a levé une instance de l'exception `%2$s`."
#: src/i18n.c:27
#: src/string/i18n.c:27
#, c-format
msgid "The statement `%1$s` did not throw an instance of the `%2$s` exception."
msgstr "L'instruction `%1$s` n'a pas levé d'instance de l'exception `%2$s`."

View file

@ -17,6 +17,7 @@ set(SAMPLES
theories.c
timeout.c
redirect.c
parameterized.c
signal.cc
report.cc
@ -54,6 +55,7 @@ macro(add_samples DIR_ SAMPLES_)
add_test(${sample} ${sample}.bin)
set_property(TEST ${sample} PROPERTY
ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1"
ENVIRONMENT "CRITERION_NO_EARLY_EXIT=1" # for coverage
)
if (NOT MSVC) # we disable the scripted tests when building with MSVC
@ -62,6 +64,7 @@ macro(add_samples DIR_ SAMPLES_)
ENVIRONMENT "LC_ALL=en_US.utf8"
ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1"
ENVIRONMENT "CRITERION_SHORT_FILENAME=1"
ENVIRONMENT "CRITERION_NO_EARLY_EXIT=1" # for coverage
)
endif ()
endforeach()
@ -75,6 +78,7 @@ foreach(script ${SCRIPTS})
add_test(${script} sh ${CMAKE_CURRENT_LIST_DIR}/tests/${script}.sh)
set_property(TEST ${script} PROPERTY
ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1"
ENVIRONMENT "CRITERION_NO_EARLY_EXIT=1" # for coverage
)
add_test(${script}_compare sh ${CMAKE_CURRENT_LIST_DIR}/tests/run_test.sh "${CMAKE_CURRENT_LIST_DIR}" . "${CMAKE_CURRENT_LIST_DIR}" tests/${script})
@ -82,6 +86,7 @@ foreach(script ${SCRIPTS})
ENVIRONMENT "LC_ALL=en_US.utf8"
ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1"
ENVIRONMENT "CRITERION_SHORT_FILENAME=1"
ENVIRONMENT "CRITERION_NO_EARLY_EXIT=1" # for coverage
)
endforeach()

View file

@ -0,0 +1,19 @@
[----] parameterized.c:76: Assertion failed: Parameters: (1, 2.000000)
[FAIL] params::cleanup: (0.00s)
[----] parameterized.c:76: Assertion failed: Parameters: (3, 4.000000)
[FAIL] params::cleanup: (0.00s)
[----] parameterized.c:76: Assertion failed: Parameters: (5, 6.000000)
[FAIL] params::cleanup: (0.00s)
[----] parameterized.c:36: Assertion failed: Parameters: (1, 2.000000)
[FAIL] params::multiple: (0.00s)
[----] parameterized.c:36: Assertion failed: Parameters: (3, 4.000000)
[FAIL] params::multiple: (0.00s)
[----] parameterized.c:36: Assertion failed: Parameters: (5, 6.000000)
[FAIL] params::multiple: (0.00s)
[----] parameterized.c:15: Assertion failed: Parameter: foo
[FAIL] params::str: (0.00s)
[----] parameterized.c:15: Assertion failed: Parameter: bar
[FAIL] params::str: (0.00s)
[----] parameterized.c:15: Assertion failed: Parameter: baz
[FAIL] params::str: (0.00s)
[====] Synthesis: Tested: 9 | Passing: 0 | Failing: 9 | Crashing: 0 

77
samples/parameterized.c Normal file
View file

@ -0,0 +1,77 @@
#include <criterion/parameterized.h>
#include <stdio.h>
// Basic usage
ParameterizedTestParameters(params, str) {
static const char *strings[] = {
"foo", "bar", "baz"
};
return cr_make_param_array(const char *, strings, sizeof (strings) / sizeof (const char *));
}
ParameterizedTest(const char **str, params, str) {
cr_assert_fail("Parameter: %s", *str);
}
// Multiple parameters must be coalesced in a single parameter
struct parameter_tuple {
int i;
double d;
};
ParameterizedTestParameters(params, multiple) {
static struct parameter_tuple params[] = {
{1, 2},
{3, 4},
{5, 6},
};
return cr_make_param_array(struct parameter_tuple, params, sizeof (params) / sizeof (struct parameter_tuple));
}
ParameterizedTest(struct parameter_tuple *tup, params, multiple) {
cr_assert_fail("Parameters: (%d, %f)", tup->i, tup->d);
}
// Cleaning up dynamically generated parameters
// you **MUST** use cr_malloc, cr_free, cr_realloc, and cr_calloc instead of their
// unprefixed counterparts to allocate dynamic memory in parameters, otherwise
// this will crash on Windows builds of the test.
struct parameter_tuple_dyn {
int i;
double *d;
};
void free_params(struct criterion_test_params *crp) {
for (size_t i = 0; i < crp->length; ++i) {
struct parameter_tuple_dyn *tup = (struct parameter_tuple_dyn*) crp->params + i;
cr_free(tup->d);
}
cr_free(crp->params);
}
double *gen_double(double val) {
double *ptr = cr_malloc(sizeof (double));
*ptr = val;
return ptr;
}
ParameterizedTestParameters(params, cleanup) {
const size_t nb_tuples = 3;
struct parameter_tuple_dyn *params = cr_malloc(sizeof (struct parameter_tuple_dyn) * nb_tuples);
params[0] = (struct parameter_tuple_dyn) { 1, gen_double(2) };
params[1] = (struct parameter_tuple_dyn) { 3, gen_double(4) };
params[2] = (struct parameter_tuple_dyn) { 5, gen_double(6) };
return cr_make_param_array(struct parameter_tuple_dyn, params, nb_tuples, free_params);
}
ParameterizedTest(struct parameter_tuple_dyn *tup, params, cleanup) {
cr_assert_fail("Parameters: (%d, %f)", tup->i, *tup->d);
}

View file

@ -3,13 +3,8 @@
#include <string>
#include <iostream>
#include <fstream>
#include <cctype>
#ifdef __GNUC__
# include <ext/stdio_filebuf.h>
#endif
// Testing stdout/stderr
void redirect_all_std(void) {

View file

@ -1,3 +1,3 @@
#!/bin/sh
./simple.c.bin --no-early-exit --always-succeed
./theories.c.bin --no-early-exit --always-succeed
./simple.c.bin --always-succeed
CRITERION_NO_EARLY_EXIT=0 ./simple.c.bin --always-succeed

116
src/compat/alloc.c Normal file
View file

@ -0,0 +1,116 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "alloc.h"
#include "internal.h"
#include <stdlib.h>
#ifdef VANILLA_WIN32
HANDLE g_heap;
struct garbage_heap {
HANDLE handle;
struct garbage_heap *next;
};
# define HEAP_MIN_BASE 0x08000000
void init_inheritable_heap(void) {
struct garbage_heap *heaps = NULL;
while ((void*)(g_heap = HeapCreate(0, 0, 0)) < (void*)HEAP_MIN_BASE) {
if (g_heap == NULL)
break;
struct garbage_heap *heap = malloc(sizeof(struct garbage_heap));
heap->handle = g_heap;
heap->next = heaps;
heaps = heap;
}
for (struct garbage_heap *h = heaps; h != NULL; h = h->next)
HeapDestroy(h->handle);
if (g_heap == (HANDLE) NULL) {
fputs("Could not create the private inheritable heap.", stderr);
abort();
}
}
int inherit_heap(HANDLE child_process) {
PROCESS_HEAP_ENTRY entry = { .lpData = NULL };
while (HeapWalk(g_heap, &entry)) {
if (!(entry.wFlags & PROCESS_HEAP_REGION))
continue;
DWORD region_size = entry.Region.dwCommittedSize;
if (!VirtualAllocEx(child_process,
entry.lpData,
region_size,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE))
return -1;
if (!WriteProcessMemory(child_process,
entry.lpData,
entry.lpData,
region_size,
NULL))
return -1;
}
return 0;
}
#endif
void *cr_malloc(size_t size) {
#ifdef VANILLA_WIN32
return HeapAlloc(g_heap, 0, size);
#else
return malloc(size);
#endif
}
void *cr_calloc(size_t nmemb, size_t size) {
#ifdef VANILLA_WIN32
return HeapAlloc(g_heap, HEAP_ZERO_MEMORY, nmemb * size);
#else
return calloc(nmemb, size);
#endif
}
void *cr_realloc(void *ptr, size_t size) {
#ifdef VANILLA_WIN32
return HeapReAlloc(g_heap, 0, ptr, size);
#else
return realloc(ptr, size);
#endif
}
void cr_free(void *ptr) {
#ifdef VANILLA_WIN32
HeapFree(g_heap, 0, ptr);
#else
free(ptr);
#endif
}

35
src/compat/alloc.h Normal file
View file

@ -0,0 +1,35 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef COMPAT_ALLOC_H_
# define COMPAT_ALLOC_H_
# include "criterion/alloc.h"
# include "posix.h"
# ifdef VANILLA_WIN32
void init_inheritable_heap(void);
int inherit_heap(HANDLE child_process);
# endif
#endif /* !COMPAT_ALLOC_H_ */

32
src/compat/basename.c Normal file
View file

@ -0,0 +1,32 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "basename.h"
const char *basename_compat(const char *str) {
const char *start = str;
for (const char *c = str; *c; ++c)
if ((*c == '/' || *c == '\\') && c[1])
start = c + 1;
return start;
}

29
src/compat/basename.h Normal file
View file

@ -0,0 +1,29 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef BASENAME_H_
# define BASENAME_H_
const char *basename_compat(const char *str);
#endif /* !BASENAME_H_ */

47
src/compat/internal.h Normal file
View file

@ -0,0 +1,47 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef INTERNAL_H_
# define INTERNAL_H_
# if defined(_WIN32) && !defined(__CYGWIN__)
# define VC_EXTRALEAN
# define WIN32_LEAN_AND_MEAN
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x0502
# include <windows.h>
# include <io.h>
# include <fcntl.h>
# include <winnt.h>
# include <stdint.h>
# include <signal.h>
# else
# include <unistd.h>
# include <sys/wait.h>
# include <sys/signal.h>
# include <sys/fcntl.h>
# endif
# include "posix.h"
#endif /* !INTERNAL_H_ */

183
src/compat/mockfile.c Normal file
View file

@ -0,0 +1,183 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#define _GNU_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "internal.h"
#include "criterion/redirect.h"
#ifdef __unix__
#include <sys/types.h>
# 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) {
#ifdef __unix__
struct memfile *cookie = malloc(sizeof (struct memfile));
*cookie = (struct memfile) {
.max_size = max_size,
.region_size = 4096,
.mem = malloc(4096),
};
FILE *f;
# if defined(__unix__) && !defined(BSD)
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

@ -0,0 +1,38 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef PIPE_INTERNAL_H_
# define PIPE_INTERNAL_H_
# include "internal.h"
# include "pipe.h"
struct pipe_handle {
#ifdef VANILLA_WIN32
HANDLE fhs[2];
#else
int fds[2];
#endif
};
#endif /* !PIPE_INTERNAL_H_ */

191
src/compat/pipe.c Normal file
View file

@ -0,0 +1,191 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdio.h>
#include <csptr/smalloc.h>
#include "criterion/assert.h"
#include "pipe-internal.h"
FILE *pipe_in(s_pipe_handle *p, enum pipe_opt opts) {
#ifdef VANILLA_WIN32
if (opts & PIPE_CLOSE)
CloseHandle(p->fhs[1]);
int fd = _open_osfhandle((intptr_t) p->fhs[0], _O_RDONLY);
if (fd == -1)
return NULL;
if (opts & PIPE_DUP)
fd = _dup(fd);
FILE *in = _fdopen(fd, "r");
#else
if (opts & PIPE_CLOSE)
close(p->fds[1]);
int fd = p->fds[0];
if (opts & PIPE_DUP)
fd = dup(fd);
FILE *in = fdopen(fd, "r");
#endif
if (!in)
return NULL;
setvbuf(in, NULL, _IONBF, 0);
return in;
}
FILE *pipe_out(s_pipe_handle *p, enum pipe_opt opts) {
#ifdef VANILLA_WIN32
if (opts & PIPE_CLOSE)
CloseHandle(p->fhs[0]);
int fd = _open_osfhandle((intptr_t) p->fhs[1], _O_WRONLY);
if (fd == -1)
return NULL;
if (opts & PIPE_DUP)
fd = _dup(fd);
FILE *out = _fdopen(fd, "w");
#else
if (opts & PIPE_CLOSE)
close(p->fds[0]);
int fd = p->fds[1];
if (opts & PIPE_DUP)
fd = dup(fd);
FILE *out = fdopen(fd, "w");
#endif
if (!out)
return NULL;
setvbuf(out, NULL, _IONBF, 0);
return out;
}
int stdpipe_stack(s_pipe_handle *out) {
#ifdef VANILLA_WIN32
HANDLE fhs[2];
SECURITY_ATTRIBUTES attr = {
.nLength = sizeof (SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE
};
if (!CreatePipe(fhs, fhs + 1, &attr, 0))
return -1;
*out = (s_pipe_handle) {{ fhs[0], fhs[1] }};
#else
int fds[2] = { -1, -1 };
if (pipe(fds) == -1)
return -1;
*out = (s_pipe_handle) {{ fds[0], fds[1] }};
#endif
return 0;
}
s_pipe_handle *stdpipe() {
s_pipe_handle *handle = smalloc(sizeof (s_pipe_handle));
if (stdpipe_stack(handle) < 0)
return NULL;
return handle;
}
int stdpipe_options(s_pipe_handle *handle, int id, int noblock) {
#ifdef VANILLA_WIN32
HANDLE fhs[2];
SECURITY_ATTRIBUTES attr = {
.nLength = sizeof (SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE
};
char pipe_name[256] = {0};
snprintf(pipe_name, sizeof (pipe_name),
"\\\\.\\pipe\\criterion_%lu_%d", GetCurrentProcessId(), id);
fhs[0] = CreateNamedPipe(pipe_name,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE
| (noblock ? PIPE_NOWAIT : PIPE_WAIT),
1,
4096 * 4,
4096 * 4,
0,
&attr);
if (fhs[0] == INVALID_HANDLE_VALUE)
return 0;
fhs[1] = CreateFile(pipe_name,
GENERIC_WRITE,
0,
&attr,
OPEN_EXISTING,
0,
NULL);
if (fhs[1] == INVALID_HANDLE_VALUE) {
CloseHandle(fhs[0]);
return 0;
}
*handle = (s_pipe_handle) {{ fhs[0], fhs[1] }};
#else
(void) id;
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 pipe_std_redirect(s_pipe_handle *pipe, enum criterion_std_fd fd) {
enum pipe_end end = fd == CR_STDIN ? PIPE_READ : PIPE_WRITE;
#ifdef VANILLA_WIN32
int stdfd = _open_osfhandle((intptr_t) pipe->fhs[end], end == PIPE_READ ? _O_RDONLY : _O_WRONLY);
if (stdfd == -1)
cr_assert_fail("Could not redirect standard file descriptor.");
_close(fd);
_dup2(stdfd, fd);
_close(stdfd);
setvbuf(get_std_file(fd), NULL, _IONBF, 0);
static int handles[] = {
[CR_STDIN] = STD_INPUT_HANDLE,
[CR_STDOUT] = STD_OUTPUT_HANDLE,
[CR_STDERR] = STD_ERROR_HANDLE,
};
SetStdHandle(handles[fd], pipe->fhs[end]);
#else
close(fd);
dup2(pipe->fds[end], fd);
close(pipe->fds[end]);
#endif
}
static s_pipe_handle stdout_redir_;
static s_pipe_handle stderr_redir_;
static s_pipe_handle stdin_redir_;
s_pipe_handle *stdout_redir = &stdout_redir_;
s_pipe_handle *stderr_redir = &stderr_redir_;
s_pipe_handle *stdin_redir = &stdin_redir_;

69
src/compat/pipe.h Normal file
View file

@ -0,0 +1,69 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef PIPE_H_
# define PIPE_H_
# include <stdio.h>
# include "common.h"
struct pipe_handle;
typedef struct pipe_handle s_pipe_handle;
enum pipe_end {
PIPE_READ = 0,
PIPE_WRITE = 1,
};
enum criterion_std_fd {
CR_STDIN = 0,
CR_STDOUT = 1,
CR_STDERR = 2,
};
enum pipe_opt {
PIPE_DUP = 1 << 0,
PIPE_CLOSE = 1 << 1,
};
s_pipe_handle *stdpipe();
FILE *pipe_in(s_pipe_handle *p, enum pipe_opt opts);
FILE *pipe_out(s_pipe_handle *p, enum pipe_opt opts);
int stdpipe_options(s_pipe_handle *pipe, int id, int noblock);
void pipe_std_redirect(s_pipe_handle *pipe, enum criterion_std_fd fd);
INLINE 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;
}
extern s_pipe_handle *stdout_redir;
extern s_pipe_handle *stderr_redir;
extern s_pipe_handle *stdin_redir;
#endif /* !PIPE_H_ */

67
src/compat/posix.h Normal file
View file

@ -0,0 +1,67 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef POSIX_COMPAT_H_
# define POSIX_COMPAT_H_
#if defined(_WIN32) && !defined(__CYGWIN__)
# define VANILLA_WIN32
#endif
# if defined(BSD) \
|| defined(__FreeBSD__) \
|| defined(__NetBSD__) \
|| defined(__OpenBSD__) \
|| defined(__DragonFly__)
# define OS_BSD 1
# endif
# if !defined(_POSIX_SOURCE)
# define _POSIX_SOURCE 1
# define TMP_POSIX
# endif
# include <stdio.h>
# ifdef TMP_POSIX
# undef _POSIX_SOURCE
# undef TMP_POSIX
# endif
# ifdef VANILLA_WIN32
# define WEXITSTATUS(Status) (((Status) & 0xFF00) >> 8)
# define WTERMSIG(Status) ((Status) & 0x7F)
# define WIFEXITED(Status) (WTERMSIG(Status) == 0)
# define WIFSIGNALED(Status) (((signed char) (WTERMSIG(Status) + 1) >> 1) > 0)
# define SIGPROF 27
# define CR_EXCEPTION_TIMEOUT 0xC0001042
# else
# include <sys/param.h>
# include <sys/wait.h>
# endif
# include "compat/pipe.h"
# include "compat/section.h"
# include "compat/process.h"
# include "compat/basename.h"
#endif /* !POSIX_COMPAT_H_ */

408
src/compat/process.c Normal file
View file

@ -0,0 +1,408 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <assert.h>
#include <string.h>
#include <csptr/smalloc.h>
#include "core/worker.h"
#include "core/runner.h"
#include "io/event.h"
#include "process.h"
#include "internal.h"
#include "pipe-internal.h"
#include "alloc.h"
#include <signal.h>
#ifdef VANILLA_WIN32
# include <tchar.h>
# define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \
CreateProcessW(Filename, \
CmdLine, \
NULL, \
NULL, \
TRUE, \
CREATE_SUSPENDED, \
NULL, \
NULL, \
&(StartupInfo), \
&(Info))
# define WRITE_PROCESS_(Proc, What, Size) \
WriteProcessMemory(Proc, &What, &What, Size, NULL);
static int get_win_status(HANDLE handle) {
DWORD exit_code;
GetExitCodeProcess(handle, &exit_code);
unsigned int sig = 0;
switch (exit_code) {
case STATUS_FLOAT_DENORMAL_OPERAND:
case STATUS_FLOAT_DIVIDE_BY_ZERO:
case STATUS_FLOAT_INEXACT_RESULT:
case STATUS_FLOAT_INVALID_OPERATION:
case STATUS_FLOAT_OVERFLOW:
case STATUS_FLOAT_STACK_CHECK:
case STATUS_FLOAT_UNDERFLOW:
case STATUS_INTEGER_DIVIDE_BY_ZERO:
case STATUS_INTEGER_OVERFLOW: sig = SIGFPE; break;
case STATUS_ILLEGAL_INSTRUCTION:
case STATUS_PRIVILEGED_INSTRUCTION:
case STATUS_NONCONTINUABLE_EXCEPTION: sig = SIGILL; break;
case CR_EXCEPTION_TIMEOUT: sig = SIGPROF; break;
case STATUS_ACCESS_VIOLATION:
case STATUS_DATATYPE_MISALIGNMENT:
case STATUS_ARRAY_BOUNDS_EXCEEDED:
case STATUS_GUARD_PAGE_VIOLATION:
case STATUS_IN_PAGE_ERROR:
case STATUS_NO_MEMORY:
case STATUS_INVALID_DISPOSITION:
case STATUS_STACK_OVERFLOW: sig = SIGSEGV; break;
case STATUS_CONTROL_C_EXIT: sig = SIGINT; break;
default: break;
}
return sig ? sig : exit_code << 8;
}
#endif
struct worker_context g_worker_context = {.test = NULL};
#ifdef VANILLA_WIN32
struct full_context {
struct criterion_test test;
struct criterion_test_extra_data test_data;
struct criterion_suite suite;
struct criterion_test_extra_data suite_data;
f_worker_func func;
struct pipe_handle pipe;
struct test_single_param param;
HANDLE sync;
DWORD extra_size;
};
static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker_%lu");
static struct full_context local_ctx;
#endif
#if defined(__unix__) || defined(__APPLE__)
# ifndef __GNUC__
# error Unsupported compiler. Use GCC or Clang under *nixes.
# endif
static void handle_sigchld(UNUSED int sig) {
assert(sig == SIGCHLD);
int fd = g_worker_pipe->fds[1];
pid_t pid;
int status;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
int kind = WORKER_TERMINATED;
struct worker_status ws = {
(s_proc_handle) { pid }, get_status(status)
};
char buf[sizeof (int) + sizeof (struct worker_status)];
memcpy(buf, &kind, sizeof (kind));
memcpy(buf + sizeof (kind), &ws, sizeof (ws));
if (write(fd, &buf, sizeof (buf)) < (ssize_t) sizeof (buf))
abort();
}
}
#endif
#ifdef VANILLA_WIN32
struct wait_context {
HANDLE wait_handle;
HANDLE proc_handle;
};
static void CALLBACK handle_child_terminated(PVOID lpParameter,
UNUSED BOOLEAN TimerOrWaitFired) {
assert(!TimerOrWaitFired);
struct wait_context *wctx = lpParameter;
int status = get_win_status(wctx->proc_handle);
int kind = WORKER_TERMINATED;
struct worker_status ws = {
(s_proc_handle) { wctx->proc_handle }, get_status(status)
};
char buf[sizeof (int) + sizeof (struct worker_status)];
memcpy(buf, &kind, sizeof (kind));
memcpy(buf + sizeof (kind), &ws, sizeof (ws));
DWORD written;
WriteFile(g_worker_pipe->fhs[1], buf, sizeof (buf), &written, NULL);
HANDLE whandle = wctx->wait_handle;
free(lpParameter);
UnregisterWaitEx(whandle, NULL);
}
#endif
int resume_child(void) {
#ifdef VANILLA_WIN32
TCHAR mapping_name[128];
_sntprintf(mapping_name, 128, g_mapping_name, GetCurrentProcessId());
HANDLE sharedMem = OpenFileMapping(
FILE_MAP_ALL_ACCESS,
FALSE,
mapping_name);
if (sharedMem == NULL) {
init_inheritable_heap();
return 0;
}
struct full_context *ctx = (struct full_context *) MapViewOfFile(sharedMem,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof (struct full_context));
if (ctx == NULL) {
CloseHandle(sharedMem);
exit(-1);
}
local_ctx = *ctx;
UnmapViewOfFile(ctx);
struct test_single_param *param = NULL;
if (local_ctx.param.size != 0) {
ctx = (struct full_context*) MapViewOfFile(sharedMem,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof (struct full_context) + local_ctx.extra_size);
if (ctx == NULL) {
CloseHandle(sharedMem);
exit(-1);
}
param = malloc(sizeof (struct test_single_param) + local_ctx.param.size);
*param = (struct test_single_param) {
.size = local_ctx.param.size,
.ptr = param + 1,
};
memcpy(param + 1, ctx + 1, param->size);
UnmapViewOfFile(ctx);
}
CloseHandle(sharedMem);
g_worker_context = (struct worker_context) {
.test = &local_ctx.test,
.suite = &local_ctx.suite,
.func = local_ctx.func,
.pipe = &local_ctx.pipe,
.param = param,
};
local_ctx.test.data = &local_ctx.test_data;
local_ctx.suite.data = &local_ctx.suite_data;
SetEvent(local_ctx.sync);
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
run_worker(&g_worker_context);
free(param);
return 1;
#else
# if defined(__unix__) || defined(__APPLE__)
struct sigaction sa;
sa.sa_handler = &handle_sigchld;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, 0) == -1) {
perror(0);
exit(1);
}
# endif
return 0;
#endif
}
s_proc_handle *fork_process() {
#ifdef VANILLA_WIN32
PROCESS_INFORMATION info;
STARTUPINFOW si = { .cb = sizeof (STARTUPINFOW) };
ZeroMemory(&info, sizeof (info));
SECURITY_ATTRIBUTES inherit_handle = {
.nLength = sizeof (SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE
};
// Create the synchronization event
HANDLE sync = CreateEvent(&inherit_handle, FALSE, FALSE, NULL);
if (sync == NULL)
return (void *) -1;
// Create the suspended child process
wchar_t filename[MAX_PATH];
GetModuleFileNameW(NULL, filename, MAX_PATH);
if (!CREATE_SUSPENDED_(filename, GetCommandLineW(), si, info))
return (void *) -1;
// Copy context over
TCHAR mapping_name[128];
_sntprintf(mapping_name, 128, g_mapping_name, info.dwProcessId);
DWORD mapping_size = sizeof (struct full_context);
if (g_worker_context.param)
mapping_size += g_worker_context.param->size;
HANDLE sharedMem = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
mapping_size,
mapping_name);
if (sharedMem == NULL || GetLastError() == ERROR_ALREADY_EXISTS)
return (void *) -1;
struct full_context *ctx = (struct full_context *) MapViewOfFile(sharedMem,
FILE_MAP_ALL_ACCESS,
0,
0,
mapping_size);
if (ctx == NULL) {
CloseHandle(sharedMem);
return (void *) -1;
}
*ctx = (struct full_context) {
.test = *g_worker_context.test,
.test_data = *g_worker_context.test->data,
.suite = *g_worker_context.suite,
.func = g_worker_context.func,
.pipe = *g_worker_context.pipe,
.sync = sync,
};
if (g_worker_context.param) {
ctx->extra_size = g_worker_context.param->size;
ctx->param = *g_worker_context.param;
memcpy(ctx + 1, g_worker_context.param->ptr, g_worker_context.param->size);
}
if (g_worker_context.suite->data)
ctx->suite_data = *g_worker_context.suite->data;
inherit_heap(info.hProcess);
if (ResumeThread(info.hThread) == (DWORD) -1)
goto failure;
// wait until the child has initialized itself
HANDLE handles[] = { info.hProcess, sync };
DWORD wres = WaitForMultipleObjects(sizeof (handles) / sizeof (HANDLE), handles, FALSE, INFINITE);
if (wres == WAIT_OBJECT_0)
goto failure;
CloseHandle(info.hThread);
UnmapViewOfFile(ctx);
CloseHandle(sharedMem);
struct wait_context *wctx = malloc(sizeof (struct wait_context));
*wctx = (struct wait_context) {
.proc_handle = info.hProcess,
};
RegisterWaitForSingleObject(
&wctx->wait_handle,
info.hProcess,
handle_child_terminated,
wctx,
INFINITE,
WT_EXECUTELONGFUNCTION | WT_EXECUTEONLYONCE);
s_proc_handle *handle = smalloc(sizeof (s_proc_handle));
*handle = (s_proc_handle) { info.hProcess };
return handle;
failure:
UnmapViewOfFile(ctx);
CloseHandle(sharedMem);
CloseHandle(info.hThread);
CloseHandle(info.hProcess);
return (void *) -1;
#else
pid_t pid = fork();
if (pid == -1)
return (void *) -1;
if (pid == 0)
return NULL;
s_proc_handle *handle = smalloc(sizeof (s_proc_handle));
*handle = (s_proc_handle) { pid };
return handle;
#endif
}
void wait_process(s_proc_handle *handle, int *status) {
#ifdef VANILLA_WIN32
WaitForSingleObject(handle->handle, INFINITE);
*status = get_win_status(handle->handle);
CloseHandle(handle->handle);
#else
waitpid(handle->pid, status, 0);
#endif
}
s_proc_handle *get_current_process() {
s_proc_handle *handle = smalloc(sizeof (s_proc_handle));
#ifdef VANILLA_WIN32
*handle = (s_proc_handle) { GetCurrentProcess() };
#else
*handle = (s_proc_handle) { getpid() };
#endif
return handle;
}
bool is_current_process(s_proc_handle *proc) {
#ifdef VANILLA_WIN32
return GetProcessId(proc->handle) == GetProcessId(GetCurrentProcess());
#else
return proc->pid == getpid();
#endif
}

58
src/compat/process.h Normal file
View file

@ -0,0 +1,58 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef COMPAT_PROCESS_H_
# define COMPAT_PROCESS_H_
# include "criterion/types.h"
# include "internal.h"
struct proc_handle {
#ifdef VANILLA_WIN32
HANDLE handle;
#else
pid_t pid;
#endif
};
typedef struct proc_handle s_proc_handle;
struct worker_context {
struct criterion_test *test;
struct criterion_suite *suite;
f_worker_func func;
struct pipe_handle *pipe;
struct test_single_param *param;
};
extern struct worker_context g_worker_context;
int resume_child(void);
s_proc_handle *fork_process();
void wait_process(s_proc_handle *handle, int *status);
s_proc_handle *get_current_process();
bool is_current_process(s_proc_handle *proc);
#endif /* !COMPAT_PROCESS_H_ */

88
src/compat/section.c Normal file
View file

@ -0,0 +1,88 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <assert.h>
#include "section.h"
#include "internal.h"
#ifdef _WIN32
void *get_win_section_start(const char *section) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) GetModuleHandle(NULL);
PIMAGE_NT_HEADERS ntHeader = ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)(dosHeader) + (dosHeader->e_lfanew));
assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE);
assert(ntHeader->Signature == IMAGE_NT_SIGNATURE);
PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(ntHeader);
for(int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++, pSecHeader++) {
if (!strncmp((char*) pSecHeader->Name, section, 8)) {
return (char*) dosHeader + pSecHeader->VirtualAddress;
}
}
return NULL;
}
void *get_win_section_end(const char *section) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) GetModuleHandle(NULL);
PIMAGE_NT_HEADERS ntHeader = ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)(dosHeader) + (dosHeader->e_lfanew));
assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE);
assert(ntHeader->Signature == IMAGE_NT_SIGNATURE);
PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(ntHeader);
for(int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++, pSecHeader++) {
if (!strncmp((char*) pSecHeader->Name, section, 8)) {
return (char*) dosHeader + (size_t) pSecHeader->VirtualAddress + pSecHeader->SizeOfRawData;
}
}
return NULL;
}
#endif
#ifdef __APPLE__
# include <mach-o/getsect.h>
# include <mach-o/dyld.h>
# define BASE_IMAGE_INDEX 0
static inline void *get_real_address(void *addr) {
if (!addr)
return NULL;
// We need to slide the section address to get a valid pointer
// because ASLR will shift the image by a random offset
return addr + _dyld_get_image_vmaddr_slide(BASE_IMAGE_INDEX);
}
void *get_osx_section_start(const char *section) {
unsigned long secsize;
return get_real_address(getsectdata("__DATA", section, &secsize));
}
void *get_osx_section_end(const char *section) {
unsigned long secsize;
char *section_start = getsectdata("__DATA", section, &secsize);
return get_real_address(section_start) + secsize;
}
#endif

View file

@ -21,63 +21,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef POSIX_COMPAT_H_
# define POSIX_COMPAT_H_
#if defined(_WIN32) && !defined(__CYGWIN__)
# define VANILLA_WIN32
#endif
# if !defined(_POSIX_SOURCE)
# define _POSIX_SOURCE 1
# define TMP_POSIX
# endif
# include <stdio.h>
# ifdef TMP_POSIX
# undef _POSIX_SOURCE
# undef TMP_POSIX
# endif
# ifdef VANILLA_WIN32
# define WEXITSTATUS(Status) (((Status) & 0xFF00) >> 8)
# define WTERMSIG(Status) ((Status) & 0x7F)
# define WIFEXITED(Status) (WTERMSIG(Status) == 0)
# define WIFSIGNALED(Status) (((signed char) (WTERMSIG(Status) + 1) >> 1) > 0)
# define SIGPROF 27
# define CR_EXCEPTION_TIMEOUT 0xC0001042
# else
# include <sys/wait.h>
# endif
#include <criterion/types.h>
struct proc_handle;
typedef struct proc_handle s_proc_handle;
struct pipe_handle;
typedef struct pipe_handle s_pipe_handle;
struct worker_context {
struct criterion_test *test;
struct criterion_suite *suite;
f_worker_func func;
struct pipe_handle *pipe;
};
extern struct worker_context g_worker_context;
int resume_child(void);
s_pipe_handle *stdpipe();
FILE *pipe_in(s_pipe_handle *p, int do_close);
FILE *pipe_out(s_pipe_handle *p, int do_close);
s_proc_handle *fork_process();
void wait_process(s_proc_handle *handle, int *status);
s_proc_handle *get_current_process();
bool is_current_process(s_proc_handle *proc);
#ifndef SECTION_H_
# define SECTION_H_
# ifdef _WIN32
void *get_win_section_start(const char *section);
@ -98,6 +43,4 @@ void *get_osx_section_end(const char *section);
# define GET_SECTION_END(Name) SECTION_END(Name)
# endif
const char *basename_compat(const char *str);
#endif /* !POSIX_COMPAT_H_ */
#endif /* !SECTION_H_ */

View file

@ -1,9 +1,9 @@
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include "timer.h"
#include "criterion/common.h"
#include "posix-compat.h"
#include "compat/time.h"
#include "compat/posix.h"
#define GIGA 1000000000

View file

@ -31,7 +31,7 @@
#include "criterion/ordered-set.h"
#include "report.h"
#include "config.h"
#include "posix-compat.h"
#include "compat/posix.h"
static inline void nothing() {}

View file

@ -28,20 +28,20 @@
#include "criterion/options.h"
#include "criterion/ordered-set.h"
#include "criterion/logging.h"
#include "compat/time.h"
#include "compat/posix.h"
#include "string/i18n.h"
#include "io/event.h"
#include "stats.h"
#include "runner.h"
#include "report.h"
#include "event.h"
#include "process.h"
#include "timer.h"
#include "posix-compat.h"
#include "worker.h"
#include "abort.h"
#include "config.h"
#include "i18n.h"
#include "common.h"
#ifdef HAVE_PCRE
#include "extmatch.h"
#include "string/extmatch.h"
#endif
#ifdef _MSC_VER
@ -186,7 +186,14 @@ static void run_test_child(struct criterion_test *test,
struct timespec_compat ts;
if (!setjmp(g_pre_test)) {
timer_start(&ts);
(test->test ? test->test : nothing)();
if (test->test) {
if (!test->data->param_) {
test->test();
} else {
void(*param_test_func)(void *) = (void(*)(void*)) test->test;
param_test_func(g_worker_context.param->ptr);
}
}
}
double elapsed_time;
@ -208,17 +215,97 @@ static INLINE bool is_disabled(struct criterion_test *t,
#define push_event(Kind, ...) \
do { \
stat_push_event(stats, \
suite_stats, \
test_stats, \
stat_push_event(ctx->stats, \
ctx->suite_stats, \
ctx->test_stats, \
&(struct event) { .kind = Kind, __VA_ARGS__ }); \
report(Kind, test_stats); \
report(Kind, ctx->test_stats); \
} while (0)
s_pipe_handle *g_worker_pipe;
struct execution_context {
bool test_started;
bool normal_finish;
bool cleaned_up;
struct criterion_global_stats *stats;
struct criterion_test *test;
struct criterion_test_stats *test_stats;
struct criterion_suite *suite;
struct criterion_suite_stats *suite_stats;
};
static void handle_worker_terminated(struct event *ev,
struct execution_context *ctx) {
struct worker_status *ws = ev->data;
struct process_status status = ws->status;
if (status.kind == SIGNAL) {
if (status.status == SIGPROF) {
ctx->test_stats->timed_out = true;
double elapsed_time = ctx->test->data->timeout;
if (elapsed_time == 0 && ctx->suite->data)
elapsed_time = ctx->suite->data->timeout;
push_event(POST_TEST, .data = &elapsed_time);
push_event(POST_FINI);
log(test_timeout, ctx->test_stats);
return;
}
if (ctx->normal_finish || !ctx->test_started) {
log(other_crash, ctx->test_stats);
if (!ctx->test_started) {
stat_push_event(ctx->stats,
ctx->suite_stats,
ctx->test_stats,
&(struct event) { .kind = TEST_CRASH });
}
return;
}
ctx->test_stats->signal = status.status;
if (ctx->test->data->signal == 0) {
push_event(TEST_CRASH);
log(test_crash, ctx->test_stats);
} else {
double elapsed_time = 0;
push_event(POST_TEST, .data = &elapsed_time);
log(post_test, ctx->test_stats);
push_event(POST_FINI);
log(post_fini, ctx->test_stats);
}
} else {
if ((ctx->normal_finish && !ctx->cleaned_up) || !ctx->test_started) {
log(abnormal_exit, ctx->test_stats);
if (!ctx->test_started) {
stat_push_event(ctx->stats,
ctx->suite_stats,
ctx->test_stats,
&(struct event) { .kind = TEST_CRASH });
}
return;
}
ctx->test_stats->exit_code = status.status;
if (!ctx->normal_finish) {
if (ctx->test->data->exit_code == 0) {
push_event(TEST_CRASH);
log(abnormal_exit, ctx->test_stats);
} else {
double elapsed_time = 0;
push_event(POST_TEST, .data = &elapsed_time);
log(post_test, ctx->test_stats);
push_event(POST_FINI);
log(post_fini, ctx->test_stats);
}
}
}
}
static void run_test(struct criterion_global_stats *stats,
struct criterion_suite_stats *suite_stats,
struct criterion_test *test,
struct criterion_suite *suite) {
struct criterion_suite *suite,
struct test_single_param *param) {
struct criterion_test_stats *test_stats = test_stats_init(test);
struct process *proc = NULL;
@ -231,16 +318,21 @@ static void run_test(struct criterion_global_stats *stats,
goto cleanup;
}
proc = spawn_test_worker(test, suite, run_test_child);
proc = spawn_test_worker(test, suite, run_test_child, g_worker_pipe, param);
if (proc == NULL && !is_runner())
goto cleanup;
bool test_started = false;
bool normal_finish = false;
bool cleaned_up = false;
struct execution_context ctx = {
.stats = stats,
.test = test,
.test_stats = test_stats,
.suite = suite,
.suite_stats = suite_stats,
};
struct event *ev;
while ((ev = worker_read_event(proc)) != NULL) {
stat_push_event(stats, suite_stats, test_stats, ev);
if (ev->kind < WORKER_TERMINATED)
stat_push_event(stats, suite_stats, test_stats, ev);
switch (ev->kind) {
case PRE_INIT:
report(PRE_INIT, test);
@ -249,7 +341,7 @@ static void run_test(struct criterion_global_stats *stats,
case PRE_TEST:
report(PRE_TEST, test);
log(pre_test, test);
test_started = true;
ctx.test_started = true;
break;
case THEORY_FAIL: {
struct criterion_theory_stats ths = {
@ -266,82 +358,65 @@ static void run_test(struct criterion_global_stats *stats,
case POST_TEST:
report(POST_TEST, test_stats);
log(post_test, test_stats);
normal_finish = true;
ctx.normal_finish = true;
break;
case POST_FINI:
report(POST_FINI, test_stats);
log(post_fini, test_stats);
cleaned_up = true;
ctx.cleaned_up = true;
break;
case WORKER_TERMINATED:
handle_worker_terminated(ev, &ctx);
sfree(ev);
goto cleanup;
}
sfree(ev);
}
struct process_status status = wait_proc(proc);
if (status.kind == SIGNAL) {
if (status.status == SIGPROF) {
test_stats->timed_out = true;
double elapsed_time = test->data->timeout;
if (elapsed_time == 0 && suite->data)
elapsed_time = suite->data->timeout;
push_event(POST_TEST, .data = &elapsed_time);
push_event(POST_FINI);
log(test_timeout, test_stats);
goto cleanup;
}
if (normal_finish || !test_started) {
log(other_crash, test_stats);
if (!test_started) {
stat_push_event(stats,
suite_stats,
test_stats,
&(struct event) { .kind = TEST_CRASH });
}
goto cleanup;
}
test_stats->signal = status.status;
if (test->data->signal == 0) {
push_event(TEST_CRASH);
log(test_crash, test_stats);
} else {
double elapsed_time = 0;
push_event(POST_TEST, .data = &elapsed_time);
log(post_test, test_stats);
push_event(POST_FINI);
log(post_fini, test_stats);
}
} else {
if ((normal_finish && !cleaned_up) || !test_started) {
log(abnormal_exit, test_stats);
if (!test_started) {
stat_push_event(stats,
suite_stats,
test_stats,
&(struct event) { .kind = TEST_CRASH });
}
goto cleanup;
}
test_stats->exit_code = status.status;
if (!normal_finish) {
if (test->data->exit_code == 0) {
push_event(TEST_CRASH);
log(abnormal_exit, test_stats);
} else {
double elapsed_time = 0;
push_event(POST_TEST, .data = &elapsed_time);
log(post_test, test_stats);
push_event(POST_FINI);
log(post_fini, test_stats);
}
}
}
cleanup:
sfree(test_stats);
sfree(proc);
}
static void run_test_param(struct criterion_global_stats *stats,
struct criterion_suite_stats *suite_stats,
struct criterion_test *test,
struct criterion_suite *suite) {
if (!test->data->param_)
return;
struct criterion_test_params params = test->data->param_();
for (size_t i = 0; i < params.length; ++i) {
struct test_single_param param = { params.size, (char *) params.params + i * params.size };
run_test(stats, suite_stats, test, suite, &param);
if (criterion_options.fail_fast && stats->tests_failed > 0)
break;
if (!is_runner())
break;
}
if (params.cleanup)
params.cleanup(&params);
}
static void run_test_switch(struct criterion_global_stats *stats,
struct criterion_suite_stats *suite_stats,
struct criterion_test *test,
struct criterion_suite *suite) {
switch (test->data->kind_) {
case CR_TEST_NORMAL:
run_test(stats, suite_stats, test, suite, NULL);
break;
case CR_TEST_PARAMETERIZED:
run_test_param(stats, suite_stats, test, suite);
break;
default: break;
}
}
#ifdef HAVE_PCRE
void disable_unmatching(struct criterion_test_set *set) {
FOREACH_SET(struct criterion_suite_set *s, set->suites) {
@ -381,8 +456,12 @@ static int criterion_run_all_tests_impl(struct criterion_test_set *set) {
fflush(NULL); // flush everything before forking
g_worker_pipe = stdpipe();
if (g_worker_pipe == NULL)
abort();
struct criterion_global_stats *stats = stats_init();
map_tests(set, stats, run_test);
map_tests(set, stats, run_test_switch);
int result = is_runner() ? stats->tests_failed == 0 : -1;
@ -393,6 +472,7 @@ static int criterion_run_all_tests_impl(struct criterion_test_set *set) {
log(post_all, stats);
cleanup:
sfree(g_worker_pipe);
sfree(stats);
return result;
}

View file

@ -25,7 +25,7 @@
# define CRITERION_RUNNER_H_
# include "criterion/types.h"
# include "posix-compat.h"
# include "compat/pipe.h"
DECL_SECTION_LIMITS(struct criterion_test*, cr_tst);
DECL_SECTION_LIMITS(struct criterion_suite*, cr_sts);
@ -42,4 +42,6 @@ struct criterion_test_set *criterion_init(void);
Suite < (struct criterion_suite**) GET_SECTION_END(cr_sts); \
++Suite)
extern s_pipe_handle *g_worker_pipe;
#endif /* !CRITERION_RUNNER_H_ */

View file

@ -25,7 +25,7 @@
# define STATS_H_
# include "criterion/stats.h"
# include "event.h"
# include "io/event.h"
struct criterion_global_stats *stats_init(void);
struct criterion_test_stats *test_stats_init(struct criterion_test *t);

View file

@ -86,10 +86,6 @@ void cr_theory_abort(void) {
longjmp(theory_jmp, 1);
}
int cr_theory_mark(void) {
return setjmp(theory_jmp);
}
void cr_theory_reset(struct criterion_theory_context *ctx) {
dcReset(ctx->vm);
}
@ -184,15 +180,23 @@ static void concat_arg(char (*msg)[4096], struct criterion_datapoints *dps, size
strncat(*msg, arg, sizeof (*msg) - 1);
}
int try_call_theory(struct criterion_theory_context *ctx, void (*fnptr)(void)) {
if (!setjmp(g_pre_test)) {
cr_theory_call(ctx, fnptr);
return 1;
}
return 0;
}
void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (*fnptr)(void)) {
struct criterion_theory_context *ctx = cr_theory_init();
size_t *indices = malloc(sizeof (size_t) * datapoints);
memset(indices, 0, datapoints * sizeof (size_t));
bool has_next = true;
volatile bool has_next = true;
while (has_next) {
if (!cr_theory_mark()) {
if (!setjmp(theory_jmp)) {
cr_theory_reset(ctx);
for (size_t i = 0; i < datapoints; ++i) {
@ -205,11 +209,7 @@ void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (*
((char*) dps[i].arr) + dps[i].size * indices[i]);
}
jmp_buf backup;
memcpy(backup, g_pre_test, sizeof (jmp_buf));
if (!setjmp(g_pre_test)) {
cr_theory_call(ctx, fnptr);
} else {
if (!try_call_theory(ctx, fnptr)) {
struct {
size_t len;
char msg[4096];
@ -224,7 +224,6 @@ void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (*
send_event(THEORY_FAIL, &result, result.len + sizeof (size_t));
}
memcpy(g_pre_test, backup, sizeof (jmp_buf));
}
for (size_t i = 0; i < datapoints; ++i) {

View file

@ -28,9 +28,9 @@
#include "criterion/types.h"
#include "criterion/options.h"
#include "criterion/redirect.h"
#include "process.h"
#include "event.h"
#include "posix-compat.h"
#include "io/event.h"
#include "compat/posix.h"
#include "worker.h"
struct process {
s_proc_handle *proc;
@ -63,7 +63,7 @@ struct event *worker_read_event(struct process *proc) {
void run_worker(struct worker_context *ctx) {
cr_redirect_stdin();
g_event_pipe = pipe_out(ctx->pipe, 1);
g_event_pipe = pipe_out(ctx->pipe, PIPE_CLOSE);
ctx->func(ctx->test, ctx->suite);
fclose(g_event_pipe);
@ -76,26 +76,25 @@ void run_worker(struct worker_context *ctx) {
struct process *spawn_test_worker(struct criterion_test *test,
struct criterion_suite *suite,
f_worker_func func) {
s_pipe_handle *pipe = stdpipe();
if (pipe == NULL)
abort();
f_worker_func func,
s_pipe_handle *pipe,
struct test_single_param *param) {
g_worker_context = (struct worker_context) {
.test = test,
.suite = suite,
.func = func,
.pipe = pipe
.pipe = pipe,
.param = param,
};
struct process *ptr = NULL;
s_proc_handle *proc = fork_process();
if (proc == (void *) -1) {
goto cleanup;
abort();
} else if (proc == NULL) {
run_worker(&g_worker_context);
goto cleanup;
return NULL;
}
ptr = smalloc(
@ -103,16 +102,11 @@ struct process *spawn_test_worker(struct criterion_test *test,
.kind = UNIQUE,
.dtor = close_process);
*ptr = (struct process) { .proc = proc, .in = pipe_in(pipe, 1) };
cleanup:
sfree(pipe);
*ptr = (struct process) { .proc = proc, .in = pipe_in(pipe, PIPE_DUP) };
return ptr;
}
struct process_status wait_proc(struct process *proc) {
int status;
wait_process(proc->proc, &status);
struct process_status get_status(int status) {
if (WIFEXITED(status))
return (struct process_status) {
.kind = EXIT_STATUS,
@ -127,3 +121,10 @@ struct process_status wait_proc(struct process *proc) {
return (struct process_status) { .kind = STOPPED };
}
struct process_status wait_proc(struct process *proc) {
int status;
wait_process(proc->proc, &status);
return get_status(status);
}

View file

@ -25,7 +25,9 @@
# define PROCESS_H_
# include <stdbool.h>
# include "posix-compat.h"
# include "criterion/types.h"
# include "compat/process.h"
# include "compat/pipe.h"
struct process;
@ -40,14 +42,27 @@ struct process_status {
int status;
};
struct worker_status {
s_proc_handle proc;
struct process_status status;
};
struct test_single_param {
size_t size;
void *ptr;
};
void run_worker(struct worker_context *ctx);
void set_runner_process(void);
void unset_runner_process(void);
bool is_runner(void);
struct process_status wait_proc(struct process *proc);
struct process_status get_status(int status);
struct process *spawn_test_worker(struct criterion_test *test,
struct criterion_suite *suite,
void (*func)(struct criterion_test *, struct criterion_suite *));
f_worker_func func,
s_pipe_handle *pipe,
struct test_single_param *param);
struct event *worker_read_event(struct process *proc);
#endif /* !PROCESS_H_ */

View file

@ -29,7 +29,7 @@
#include "criterion/criterion.h"
#include "criterion/options.h"
#include "criterion/ordered-set.h"
#include "runner.h"
#include "core/runner.h"
#include "config.h"
#include "common.h"

View file

@ -28,6 +28,7 @@
#include "criterion/stats.h"
#include "criterion/common.h"
#include "criterion/hooks.h"
#include "core/worker.h"
#include "event.h"
FILE *g_event_pipe = NULL;
@ -112,6 +113,20 @@ fail_assert:
*ev = (struct event) { .kind = kind, .data = elapsed_time };
return ev;
}
case WORKER_TERMINATED: {
struct worker_status *status = malloc(sizeof (struct worker_status));
if (fread(status, sizeof (struct worker_status), 1, f) == 0) {
free(status);
return NULL;
}
struct event *ev = smalloc(
.size = sizeof (struct event),
.dtor = destroy_event
);
*ev = (struct event) { .kind = kind, .data = status };
return ev;
}
default: {
struct event *ev = smalloc(sizeof (struct event));
*ev = (struct event) { .kind = kind, .data = NULL };

View file

@ -34,6 +34,10 @@ struct event {
void *data;
};
enum other_event_kinds {
WORKER_TERMINATED = 1 << 30,
};
struct event *read_event(FILE *f);
#endif /* !EVENT_H_ */

View file

@ -24,6 +24,9 @@ int cr_file_match_str(FILE* f, const char *str) {
}
int cr_file_match_file(FILE* f, FILE* ref) {
if (f == ref)
return true;
char buf1[512];
char buf2[512];

77
src/io/redirect.c Normal file
View file

@ -0,0 +1,77 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdio.h>
#include "criterion/assert.h"
#include "criterion/redirect.h"
#include "compat/pipe.h"
void cr_redirect(enum criterion_std_fd fd_kind, s_pipe_handle *pipe) {
fflush(get_std_file(fd_kind));
if (!stdpipe_options(pipe, fd_kind, fd_kind == CR_STDIN ? 0 : 1))
cr_assert_fail("Could not redirect standard file descriptor.");
pipe_std_redirect(pipe, fd_kind);
}
void cr_redirect_stdout(void) {
cr_redirect(CR_STDOUT, stdout_redir);
}
void cr_redirect_stderr(void) {
cr_redirect(CR_STDERR, stderr_redir);
}
void cr_redirect_stdin(void) {
cr_redirect(CR_STDIN, stdin_redir);
}
FILE* cr_get_redirected_stdout(void) {
static FILE *f;
if (!f) {
f = pipe_in(stdout_redir, 0);
if (!f)
cr_assert_fail("Could not get redirected stdout read end.");
}
return f;
}
FILE* cr_get_redirected_stderr(void) {
static FILE *f;
if (!f) {
f = pipe_in(stderr_redir, 0);
if (!f)
cr_assert_fail("Could not get redirected stderr read end.");
}
return f;
}
FILE* cr_get_redirected_stdin(void) {
static FILE *f;
if (!f) {
f = pipe_out(stdin_redir, 0);
if (!f)
cr_assert_fail("Could not get redirected stdin write end.");
}
return f;
}

View file

@ -27,7 +27,7 @@
#include <string.h>
#include "criterion/logging.h"
#include "criterion/options.h"
#include "i18n.h"
#include "string/i18n.h"
#ifdef ENABLE_NLS
# define LOG_FORMAT "[%1$s%2$s%3$s] %4$s"

View file

@ -30,10 +30,10 @@
#include "criterion/logging.h"
#include "criterion/options.h"
#include "criterion/ordered-set.h"
#include "timer.h"
#include "compat/posix.h"
#include "compat/time.h"
#include "string/i18n.h"
#include "config.h"
#include "i18n.h"
#include "posix-compat.h"
#include "common.h"
#ifdef VANILLA_WIN32

View file

@ -29,9 +29,9 @@
#include "criterion/logging.h"
#include "criterion/options.h"
#include "criterion/ordered-set.h"
#include "timer.h"
#include "compat/posix.h"
#include "compat/time.h"
#include "config.h"
#include "posix-compat.h"
#include "common.h"
#ifdef _MSC_VER

View file

@ -1,583 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <assert.h>
#include "posix-compat.h"
#include "process.h"
#include "criterion/assert.h"
#include "criterion/redirect.h"
#ifdef VANILLA_WIN32
# define VC_EXTRALEAN
# define WIN32_LEAN_AND_MEAN
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x0502
# include <windows.h>
# include <io.h>
# include <fcntl.h>
# include <winnt.h>
# include <stdint.h>
# define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \
CreateProcessW(Filename, \
CmdLine, \
NULL, \
NULL, \
TRUE, \
CREATE_SUSPENDED, \
NULL, \
NULL, \
&(StartupInfo), \
&(Info))
# define WRITE_PROCESS_(Proc, What, Size) \
WriteProcessMemory(Proc, &What, &What, Size, NULL);
# include <signal.h>
#else
# include <unistd.h>
# include <sys/wait.h>
# include <sys/signal.h>
# include <sys/fcntl.h>
#endif
#include <csptr/smalloc.h>
struct proc_handle {
#ifdef VANILLA_WIN32
HANDLE handle;
#else
pid_t pid;
#endif
};
struct pipe_handle {
#ifdef VANILLA_WIN32
HANDLE fhs[2];
#else
int fds[2];
#endif
};
struct worker_context g_worker_context = {.test = NULL};
#ifdef VANILLA_WIN32
struct full_context {
struct criterion_test test;
struct criterion_test_extra_data test_data;
struct criterion_suite suite;
struct criterion_test_extra_data suite_data;
f_worker_func func;
struct pipe_handle pipe;
volatile int resumed;
};
static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker");
#define MAPPING_SIZE sizeof (struct full_context)
static struct full_context local_ctx;
#endif
int resume_child(void) {
#ifdef VANILLA_WIN32
HANDLE sharedMem = OpenFileMapping(
FILE_MAP_ALL_ACCESS,
FALSE,
g_mapping_name);
if (sharedMem == NULL)
return 0;
struct full_context *ctx = (struct full_context *) MapViewOfFile(sharedMem,
FILE_MAP_ALL_ACCESS,
0,
0,
MAPPING_SIZE);
if (ctx == NULL)
exit(-1);
local_ctx = *ctx;
g_worker_context = (struct worker_context) {
&local_ctx.test,
&local_ctx.suite,
local_ctx.func,
&local_ctx.pipe
};
local_ctx.test.data = &local_ctx.test_data;
local_ctx.suite.data = &local_ctx.suite_data;
ctx->resumed = 1;
UnmapViewOfFile(ctx);
CloseHandle(sharedMem);
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
run_worker(&g_worker_context);
return 1;
#else
return 0;
#endif
}
s_proc_handle *fork_process() {
#ifdef VANILLA_WIN32
PROCESS_INFORMATION info;
STARTUPINFOW si = { .cb = sizeof (STARTUPINFOW) };
ZeroMemory(&info, sizeof (info));
// Create the suspended child process
wchar_t filename[MAX_PATH];
GetModuleFileNameW(NULL, filename, MAX_PATH);
if (!CREATE_SUSPENDED_(filename, GetCommandLineW(), si, info))
return (void *) -1;
// Copy context over
HANDLE sharedMem = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
MAPPING_SIZE,
g_mapping_name);
if (sharedMem == NULL)
return (void *) -1;
struct full_context *ctx = (struct full_context *) MapViewOfFile(sharedMem,
FILE_MAP_ALL_ACCESS,
0,
0,
MAPPING_SIZE);
if (ctx == NULL) {
CloseHandle(sharedMem);
return (void *) -1;
}
*ctx = (struct full_context) {
.test = *g_worker_context.test,
.test_data = *g_worker_context.test->data,
.suite = *g_worker_context.suite,
.func = g_worker_context.func,
.pipe = *g_worker_context.pipe,
.resumed = 0,
};
if (g_worker_context.suite->data)
ctx->suite_data = *g_worker_context.suite->data;
ResumeThread(info.hThread);
CloseHandle(info.hThread);
while (!ctx->resumed); // wait until the child has initialized itself
UnmapViewOfFile(ctx);
CloseHandle(sharedMem);
s_proc_handle *handle = smalloc(sizeof (s_proc_handle));
*handle = (s_proc_handle) { info.hProcess };
return handle;
#else
pid_t pid = fork();
if (pid == -1)
return (void *) -1;
if (pid == 0)
return NULL;
s_proc_handle *handle = smalloc(sizeof (s_proc_handle));
*handle = (s_proc_handle) { pid };
return handle;
#endif
}
void wait_process(s_proc_handle *handle, int *status) {
#ifdef VANILLA_WIN32
WaitForSingleObject(handle->handle, INFINITE);
DWORD exit_code;
GetExitCodeProcess(handle->handle, &exit_code);
CloseHandle(handle->handle);
unsigned int sig = 0;
switch (exit_code) {
case STATUS_FLOAT_DENORMAL_OPERAND:
case STATUS_FLOAT_DIVIDE_BY_ZERO:
case STATUS_FLOAT_INEXACT_RESULT:
case STATUS_FLOAT_INVALID_OPERATION:
case STATUS_FLOAT_OVERFLOW:
case STATUS_FLOAT_STACK_CHECK:
case STATUS_FLOAT_UNDERFLOW:
case STATUS_INTEGER_DIVIDE_BY_ZERO:
case STATUS_INTEGER_OVERFLOW: sig = SIGFPE; break;
case STATUS_ILLEGAL_INSTRUCTION:
case STATUS_PRIVILEGED_INSTRUCTION:
case STATUS_NONCONTINUABLE_EXCEPTION: sig = SIGILL; break;
case CR_EXCEPTION_TIMEOUT: sig = SIGPROF; break;
case STATUS_ACCESS_VIOLATION:
case STATUS_DATATYPE_MISALIGNMENT:
case STATUS_ARRAY_BOUNDS_EXCEEDED:
case STATUS_GUARD_PAGE_VIOLATION:
case STATUS_IN_PAGE_ERROR:
case STATUS_NO_MEMORY:
case STATUS_INVALID_DISPOSITION:
case STATUS_STACK_OVERFLOW: sig = SIGSEGV; break;
case STATUS_CONTROL_C_EXIT: sig = SIGINT; break;
default: break;
}
*status = sig ? sig : exit_code << 8;
#else
waitpid(handle->pid, status, 0);
#endif
}
FILE *pipe_in(s_pipe_handle *p, int do_close) {
#ifdef VANILLA_WIN32
if (do_close)
CloseHandle(p->fhs[1]);
int fd = _open_osfhandle((intptr_t) p->fhs[0], _O_RDONLY);
if (fd == -1)
return NULL;
FILE *in = _fdopen(fd, "r");
#else
if (do_close)
close(p->fds[1]);
FILE *in = fdopen(p->fds[0], "r");
#endif
if (!in)
return NULL;
setvbuf(in, NULL, _IONBF, 0);
return in;
}
FILE *pipe_out(s_pipe_handle *p, int do_close) {
#ifdef VANILLA_WIN32
if (do_close)
CloseHandle(p->fhs[0]);
int fd = _open_osfhandle((intptr_t) p->fhs[1], _O_WRONLY);
if (fd == -1)
return NULL;
FILE *out = _fdopen(fd, "w");
#else
if (do_close)
close(p->fds[0]);
FILE *out = fdopen(p->fds[1], "w");
#endif
if (!out)
return NULL;
setvbuf(out, NULL, _IONBF, 0);
return out;
}
int stdpipe_stack(s_pipe_handle *out) {
#ifdef VANILLA_WIN32
HANDLE fhs[2];
SECURITY_ATTRIBUTES attr = {
.nLength = sizeof (SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE
};
if (!CreatePipe(fhs, fhs + 1, &attr, 0))
return -1;
*out = (s_pipe_handle) {{ fhs[0], fhs[1] }};
#else
int fds[2] = { -1, -1 };
if (pipe(fds) == -1)
return -1;
*out = (s_pipe_handle) {{ fds[0], fds[1] }};
#endif
return 0;
}
s_pipe_handle *stdpipe() {
s_pipe_handle *handle = smalloc(sizeof (s_pipe_handle));
if (stdpipe_stack(handle) < 0)
return NULL;
return handle;
}
s_proc_handle *get_current_process() {
s_proc_handle *handle = smalloc(sizeof (s_proc_handle));
#ifdef VANILLA_WIN32
*handle = (s_proc_handle) { GetCurrentProcess() };
#else
*handle = (s_proc_handle) { getpid() };
#endif
return handle;
}
bool is_current_process(s_proc_handle *proc) {
#ifdef VANILLA_WIN32
return GetProcessId(proc->handle) == GetProcessId(GetCurrentProcess());
#else
return proc->pid == getpid();
#endif
}
#ifdef _WIN32
void *get_win_section_start(const char *section) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) GetModuleHandle(NULL);
PIMAGE_NT_HEADERS ntHeader = ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)(dosHeader) + (dosHeader->e_lfanew));
assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE);
assert(ntHeader->Signature == IMAGE_NT_SIGNATURE);
PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(ntHeader);
for(int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++, pSecHeader++) {
if (!strncmp((char*) pSecHeader->Name, section, 8)) {
return (char*) dosHeader + pSecHeader->VirtualAddress;
}
}
return NULL;
}
void *get_win_section_end(const char *section) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) GetModuleHandle(NULL);
PIMAGE_NT_HEADERS ntHeader = ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)(dosHeader) + (dosHeader->e_lfanew));
assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE);
assert(ntHeader->Signature == IMAGE_NT_SIGNATURE);
PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(ntHeader);
for(int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++, pSecHeader++) {
if (!strncmp((char*) pSecHeader->Name, section, 8)) {
return (char*) dosHeader + (size_t) pSecHeader->VirtualAddress + pSecHeader->SizeOfRawData;
}
}
return NULL;
}
#endif
#ifdef __APPLE__
# include <mach-o/getsect.h>
# include <mach-o/dyld.h>
# define BASE_IMAGE_INDEX 0
static inline void *get_real_address(void *addr) {
if (!addr)
return NULL;
// We need to slide the section address to get a valid pointer
// because ASLR will shift the image by a random offset
return addr + _dyld_get_image_vmaddr_slide(BASE_IMAGE_INDEX);
}
void *get_osx_section_start(const char *section) {
unsigned long secsize;
return get_real_address(getsectdata("__DATA", section, &secsize));
}
void *get_osx_section_end(const char *section) {
unsigned long secsize;
char *section_start = getsectdata("__DATA", section, &secsize);
return get_real_address(section_start) + secsize;
}
#endif
const char *basename_compat(const char *str) {
const char *start = str;
for (const char *c = str; *c; ++c)
if ((*c == '/' || *c == '\\') && c[1])
start = c + 1;
return start;
}
#ifdef VANILLA_WIN32
typedef DWORD cr_std_fd;
#else
typedef int cr_std_fd;
#endif
static s_pipe_handle stdout_redir;
static s_pipe_handle stderr_redir;
static s_pipe_handle stdin_redir;
enum criterion_std_fd {
CR_STDIN = 0,
CR_STDOUT = 1,
CR_STDERR = 2,
};
enum criterion_pipe_end {
PIPE_READ = 0,
PIPE_WRITE = 1,
};
cr_std_fd get_std_fd(int fd_kind) {
static int kinds[] = {
#ifdef VANILLA_WIN32
[CR_STDIN] = STD_INPUT_HANDLE,
[CR_STDOUT] = STD_OUTPUT_HANDLE,
[CR_STDERR] = STD_ERROR_HANDLE,
#else
[CR_STDIN] = STDIN_FILENO,
[CR_STDOUT] = STDOUT_FILENO,
[CR_STDERR] = STDERR_FILENO,
#endif
};
return kinds[fd_kind];
}
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 id, int noblock) {
#ifdef VANILLA_WIN32
HANDLE fhs[2];
SECURITY_ATTRIBUTES attr = {
.nLength = sizeof (SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE
};
char pipe_name[256] = {0};
snprintf(pipe_name, sizeof (pipe_name),
"\\\\.\\pipe\\criterion_%lu_%d", GetCurrentProcessId(), id);
fhs[0] = CreateNamedPipe(pipe_name,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE
| (noblock ? PIPE_NOWAIT : PIPE_WAIT),
1,
4096 * 4,
4096 * 4,
0,
&attr);
if (fhs[0] == INVALID_HANDLE_VALUE)
return 0;
fhs[1] = CreateFile(pipe_name,
GENERIC_WRITE,
0,
&attr,
OPEN_EXISTING,
0,
NULL);
if (fhs[1] == INVALID_HANDLE_VALUE) {
CloseHandle(fhs[0]);
return 0;
}
*handle = (s_pipe_handle) {{ fhs[0], fhs[1] }};
#else
(void) id;
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, fd_kind, noblock))
cr_assert_fail("Could not redirect standard file descriptor.");
cr_std_fd fd = get_std_fd(fd_kind);
#ifdef VANILLA_WIN32
int stdfd = _open_osfhandle((intptr_t) pipe->fhs[fd_index], fd_kind == 0 ? _O_RDONLY : _O_WRONLY);
if (stdfd == -1)
cr_assert_fail("Could not redirect standard file descriptor.");
fflush(get_std_file(fd_kind));
_close(fd_kind);
SetStdHandle(fd, pipe->fhs[fd_index]);
_dup2(stdfd, fd_kind);
_close(stdfd);
setvbuf(get_std_file(fd_kind), NULL, _IONBF, 0);
#else
close(fd);
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, 1);
}
void cr_redirect_stderr(void) {
cr_redirect(CR_STDERR, &stderr_redir, PIPE_WRITE, 1);
}
void cr_redirect_stdin(void) {
cr_redirect(CR_STDIN, &stdin_redir, PIPE_READ, 0);
}
FILE* cr_get_redirected_stdout(void) {
static FILE *f;
if (!f) {
f = pipe_in(&stdout_redir, 0);
if (!f)
cr_assert_fail("Could not get redirected stdout read end.");
}
return f;
}
FILE* cr_get_redirected_stderr(void) {
static FILE *f;
if (!f) {
f = pipe_in(&stderr_redir, 0);
if (!f)
cr_assert_fail("Could not get redirected stderr read end.");
}
return f;
}
FILE* cr_get_redirected_stdin(void) {
static FILE *f;
if (!f) {
f = pipe_out(&stdin_redir, 0);
if (!f)
cr_assert_fail("Could not get redirected stdin write end.");
}
return f;
}

View file

@ -1,11 +1,14 @@
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
asprintf.c
redirect.cc
)
add_executable(criterion_unit_tests EXCLUDE_FROM_ALL ${TEST_SOURCES})
@ -14,3 +17,6 @@ target_link_libraries(criterion_unit_tests criterion)
add_dependencies(criterion_tests criterion_unit_tests)
add_test(criterion_unit_tests criterion_unit_tests)
set_property(TEST criterion_unit_tests PROPERTY
ENVIRONMENT "CRITERION_NO_EARLY_EXIT=1" # for coverage
)

36
test/alloc.cc Normal file
View file

@ -0,0 +1,36 @@
#include "criterion/criterion.h"
#include "criterion/alloc.h"
struct Obj {
int foo;
long bar;
Obj() {}
Obj(int foo, long bar) : foo(foo), bar(bar) {}
};
Test(alloc, object) {
Obj *o = criterion::new_obj<Obj>(42, 314);
cr_assert_not_null(o);
cr_assert_eq(o->foo, 42);
cr_assert_eq(o->bar, 314);
criterion::delete_obj(o);
}
Test(alloc, array) {
Obj *o = criterion::new_arr<Obj>(3);
cr_assert_not_null(o);
new (&o[0]) Obj(1, 2);
new (&o[1]) Obj(2, 4);
new (&o[2]) Obj(3, 6);
for (int i = 0; i < 3; ++i) {
cr_assert_eq(o[i].foo, i + 1);
cr_assert_eq(o[i].bar, 2 * (i + 1));
}
criterion::delete_arr(o);
}

65
test/asprintf.c Normal file
View file

@ -0,0 +1,65 @@
#include "criterion/criterion.h"
#include "criterion/theories.h"
#include "criterion/asprintf-compat.h"
#include <stdio.h>
union anyval {
int c;
int hd;
int d;
long ld;
long long lld;
unsigned int hu;
unsigned int u;
unsigned long lu;
unsigned long long llu;
double f;
const char *s;
void *p;
};
struct format_test {
const char *format;
union anyval *val;
};
# define VALUE(Fmt, Val) &(struct format_test) { .format = "%" #Fmt, .val = &(union anyval) { . Fmt = Val } }
TheoryDataPoints(asprintf, valid) = {
DataPoints(struct format_test *,
VALUE(c, 'a'),
VALUE(hd, 42),
VALUE(d, 42),
VALUE(ld, 42),
VALUE(lld, 42),
VALUE(hu, 42),
VALUE(u, 42),
VALUE(lu, 42),
VALUE(llu, 42),
VALUE(f, 3.14),
VALUE(s, "foo"),
VALUE(p, NULL),
),
};
Theory((struct format_test *fmt), asprintf, valid) {
char expected[32];
snprintf(expected, sizeof (expected), fmt->format, *fmt->val);
char *actual;
cr_asprintf(&actual, fmt->format, *fmt->val);
cr_expect_str_eq(actual, expected);
free(actual);
}
#if defined(__unix__) && defined(__GNUC__)
# pragma GCC diagnostic ignored "-Wformat"
Test(asprintf, invalid) {
char *actual;
cr_expect_lt(cr_asprintf(&actual, "%"), 0);
}
#endif

90
test/redirect.cc Normal file
View file

@ -0,0 +1,90 @@
#include <criterion/criterion.h>
#include <criterion/redirect.h>
#include <iostream>
#include <string>
// set a timeout for I/O tests
TestSuite(redirect, .timeout = 0.1);
#if __GNUC__ >= 5
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");
}
#endif
Test(redirect, mock_c) {
std::FILE* fmock = cr_mock_file_size(4096);
std::fprintf(fmock, "Hello");
std::fflush(fmock);
std::rewind(fmock);
char contents[sizeof ("Hello")] = {0};
fgets(contents, sizeof (contents), fmock);
cr_assert_str_eq(contents, "Hello");
}
Test(redirect, assertions) {
std::FILE* f1 = cr_mock_file_size(4096);
std::FILE* f2 = cr_mock_file_size(4096);
std::FILE* f3 = cr_mock_file_size(4096);
fprintf(f1, "Foo");
fprintf(f2, "Foo");
fprintf(f3, "Bar");
fflush(f1);
fflush(f2);
fflush(f3);
cr_expect_file_contents_eq(f1, f1);
rewind(f1);
cr_expect_file_contents_eq(f1, f2);
rewind(f1);
cr_expect_file_contents_neq(f1, f3);
fclose(f1);
fclose(f2);
fclose(f3);
}
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");
}