[v2.1.0] Merge branch 'bleeding' (Version release)
This commit is contained in:
commit
3148348c37
75 changed files with 2608 additions and 932 deletions
|
@ -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
26
.cmake/Modules/Gcov.cmake
Normal 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 ()
|
51
.travis.yml
51
.travis.yml
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
11
README.md
11
README.md
|
@ -4,7 +4,7 @@
|
|||
|
||||
[](https://travis-ci.org/Snaipe/Criterion)
|
||||
[](https://ci.appveyor.com/project/Snaipe/Criterion/branch/bleeding)
|
||||
[](https://coveralls.io/r/Snaipe/Criterion?branch=bleeding)
|
||||
[](https://codecov.io/github/Snaipe/Criterion?branch=bleeding)
|
||||
[](https://github.com/Snaipe/Criterion/blob/master/LICENSE)
|
||||
[](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)
|
||||
|
||||
|
|
12
appveyor.yml
12
appveyor.yml
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ Criterion
|
|||
assert
|
||||
hooks
|
||||
env
|
||||
parameterized
|
||||
theories
|
||||
internal
|
||||
faq
|
||||
|
|
140
doc/parameterized.rst
Normal file
140
doc/parameterized.rst
Normal 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, ¶m, 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`).
|
||||
|
||||
|
|
@ -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
100
include/criterion/alloc.h
Normal 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_ */
|
|
@ -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__, \
|
||||
|
|
|
@ -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__)) \
|
||||
|
|
50
include/criterion/parameterized.h
Normal file
50
include/criterion/parameterized.h
Normal 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_ */
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# List of source files which contain translatable strings.
|
||||
src/log/normal.c
|
||||
src/i18n.c
|
||||
src/string/i18n.c
|
||||
|
|
28
po/fr.po
28
po/fr.po
|
@ -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`."
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
19
samples/outputs/parameterized.c.bin.err.expected
Normal file
19
samples/outputs/parameterized.c.bin.err.expected
Normal file
|
@ -0,0 +1,19 @@
|
|||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m76[0m: Assertion failed: Parameters: (1, 2.000000)
|
||||
[[0;31mFAIL[0m] params::cleanup: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m76[0m: Assertion failed: Parameters: (3, 4.000000)
|
||||
[[0;31mFAIL[0m] params::cleanup: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m76[0m: Assertion failed: Parameters: (5, 6.000000)
|
||||
[[0;31mFAIL[0m] params::cleanup: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m36[0m: Assertion failed: Parameters: (1, 2.000000)
|
||||
[[0;31mFAIL[0m] params::multiple: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m36[0m: Assertion failed: Parameters: (3, 4.000000)
|
||||
[[0;31mFAIL[0m] params::multiple: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m36[0m: Assertion failed: Parameters: (5, 6.000000)
|
||||
[[0;31mFAIL[0m] params::multiple: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m15[0m: Assertion failed: Parameter: foo
|
||||
[[0;31mFAIL[0m] params::str: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m15[0m: Assertion failed: Parameter: bar
|
||||
[[0;31mFAIL[0m] params::str: (0.00s)
|
||||
[[0;34m----[0m] [0;1mparameterized.c[0m:[0;31m15[0m: Assertion failed: Parameter: baz
|
||||
[[0;31mFAIL[0m] params::str: (0.00s)
|
||||
[[0;34m====[0m] [0;1mSynthesis: Tested: [0;34m9[0;1m | Passing: [0;32m0[0;1m | Failing: [0;31m9[0;1m | Crashing: [0;31m0[0;1m [0m
|
0
samples/outputs/parameterized.c.bin.out.expected
Normal file
0
samples/outputs/parameterized.c.bin.out.expected
Normal file
77
samples/parameterized.c
Normal file
77
samples/parameterized.c
Normal 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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
116
src/compat/alloc.c
Normal 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
35
src/compat/alloc.h
Normal 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
32
src/compat/basename.c
Normal 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
29
src/compat/basename.h
Normal 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
47
src/compat/internal.h
Normal 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
183
src/compat/mockfile.c
Normal 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
|
||||
}
|
38
src/compat/pipe-internal.h
Normal file
38
src/compat/pipe-internal.h
Normal 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
191
src/compat/pipe.c
Normal 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
69
src/compat/pipe.h
Normal 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
67
src/compat/posix.h
Normal 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
408
src/compat/process.c
Normal 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
58
src/compat/process.h
Normal 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
88
src/compat/section.c
Normal 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
|
||||
|
|
@ -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_ */
|
|
@ -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
|
||||
|
|
@ -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() {}
|
||||
|
|
@ -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, ¶m);
|
||||
if (criterion_options.fail_fast && stats->tests_failed > 0)
|
||||
break;
|
||||
if (!is_runner())
|
||||
break;
|
||||
}
|
||||
|
||||
if (params.cleanup)
|
||||
params.cleanup(¶ms);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -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_ */
|
|
@ -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);
|
|
@ -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) {
|
|
@ -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);
|
||||
}
|
|
@ -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_ */
|
|
@ -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"
|
||||
|
|
@ -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 };
|
|
@ -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_ */
|
|
@ -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
77
src/io/redirect.c
Normal 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;
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
36
test/alloc.cc
Normal 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
65
test/asprintf.c
Normal 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
90
test/redirect.cc
Normal 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");
|
||||
}
|
Loading…
Add table
Reference in a new issue