[v1.2.0] Merge branch 'bleeding'

This commit is contained in:
Snaipe 2015-04-15 22:47:19 +02:00
commit ffb7e9390d
53 changed files with 1360 additions and 251 deletions

4
.ci/install-pip.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
curl -O https://bootstrap.pypa.io/get-pip.py
python3 get-pip.py
rm -f get-pip.py

1
.gitignore vendored
View file

@ -7,6 +7,7 @@
!*.h
!*.rst
!samples/tests/*.sh
!*.po
!LICENSE
!HEADER

View file

@ -4,11 +4,11 @@ compiler:
before_install:
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
- sudo apt-get -qq update
- sudo apt-get -qq install -y check gcc-4.9
- sudo apt-get -qq install -y check gcc-4.9 gettext autopoint
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 90
- sudo pip install cpp-coveralls
script:
- ./autogen.sh && ./configure --enable-gcov CFLAGS="-g -O0" && make && make check
- ./autogen.sh && ./configure --enable-gcov CFLAGS="-g -O0" && make && make -C samples check
after_success:
- coveralls --gcov gcov-4.9 --exclude samples --exclude dependencies --gcov-options '\-lp' -b .
after_failure:

View file

@ -1,5 +1,6 @@
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = dependencies/csptr samples
AM_CPPFLAGS = -DLOCALEDIR='"$(localedir)"'
SUBDIRS = po dependencies/csptr samples
lib_LTLIBRARIES = libcriterion.la
@ -14,16 +15,17 @@ libcriterion_la_CFLAGS = \
-I$(top_srcdir)/dependencies/csptr/include/ \
$(COVERAGE_CFLAGS)
libcriterion_la_LDFLAGS = $(COVERAGE_LDFLAGS)
libcriterion_la_LDFLAGS = $(COVERAGE_LDFLAGS) -version-info 1:0:0
# dirty but unless someone has a better alternative...
libcriterion_la_LIBADD = dependencies/csptr/src/libcsptr_la-*.lo
EXTRA_DIST = LICENSE
EXTRA_DIST = config.rpath LICENSE
subdirincludedir = $(includedir)/criterion/
subdirinclude_HEADERS = \
include/criterion/assert.h \
include/criterion/abort.h \
include/criterion/common.h \
include/criterion/criterion.h \
include/criterion/event.h \
@ -35,6 +37,8 @@ subdirinclude_HEADERS = \
include/criterion/stats.h
libcriterion_la_SOURCES = \
src/abort.c \
src/abort.h \
src/event.c \
src/event.h \
src/report.c \
@ -51,7 +55,10 @@ libcriterion_la_SOURCES = \
src/options.c \
src/timer.c \
src/timer.h \
src/i18n.c \
src/i18n.h \
src/ordered-set.c \
src/posix-compat.c \
src/main.c
TARGET = $(PACKAGE)-$(VERSION)

View file

@ -2,8 +2,8 @@
Criterion
=========
[![Build Status](https://travis-ci.org/Snaipe/Criterion.svg?branch=master)](https://travis-ci.org/Snaipe/Criterion)
[![Coverage Status](https://coveralls.io/repos/Snaipe/Criterion/badge.svg?branch=master)](https://coveralls.io/r/Snaipe/Criterion?branch=master)
[![Build Status](https://travis-ci.org/Snaipe/Criterion.svg?branch=bleeding)](https://travis-ci.org/Snaipe/Criterion)
[![Coverage Status](https://coveralls.io/repos/Snaipe/Criterion/badge.svg?branch=bleeding)](https://coveralls.io/r/Snaipe/Criterion?branch=bleeding)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/Snaipe/Criterion/blob/master/LICENSE)
[![Version](https://img.shields.io/github/tag/Snaipe/Criterion.svg?label=version&style=flat)](https://github.com/Snaipe/Criterion/releases)
@ -31,14 +31,14 @@ the user would have with other frameworks:
reported and tested.
* [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 (compiles only with Cygwin for the moment).
* [x] Runs on Linux, FreeBSD, Mac OS X, and Windows (Compiles only with MinGW or Cygwin).
## Downloads
* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.1.0/criterion-1.1.0-linux-x86_64.tar.bz2)
* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.1.0/criterion-1.1.0-osx-x86_64.tar.bz2)
* [Windows (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.1.0/criterion-1.1.0-win-x86_64.tar.bz2)
* [FreeBSD (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.1.0/criterion-1.1.0-freebsd-x86_64.tar.bz2)
* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-linux-x86_64.tar.bz2)
* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-osx-x86_64.tar.bz2)
* [Windows (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-win-x86_64.tar.bz2)
* [FreeBSD (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-freebsd-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)
@ -58,6 +58,34 @@ Sample tests can be found in the [sample directory][samples].
* [Tests with signals][sample-signal]
* [Using report hooks][sample-report]
## Contributing
Contributions are welcomed, but must follow a simple set of rules in order to
be merged.
**Please follow these conventions if you want your pull request(s) accepted.**
### General
* Use 4 (four) spaces for indentation.
* No trailing whitespaces.
* 80 chars column limit.
* No trash files. Trash files are by-products of the compilation process, or
generated files that does not need to be under version control.
* Pull requests must compile and work properly.
* Pull requests must be mergeable automatically.
* Number of commits in a pull request should be kept to one commit and all
additional commits must be squashed.
* You may have more than one commit in a pull request if the commits are
separate changes, otherwise squash them.
### Translations
* You can contribute new translation files for output messages, on the
condition that you are fluent with the language itself.
* Each correction on existing translations must be followed by a
rationale ("why would the translation be better if the change is applied?")
## F.A.Q.
**Q. What's wrong with other test frameworks?**
@ -72,12 +100,7 @@ A. I worked with CUnit and Check, and I must say that they do their job
**Q. Where has this been tested?**
A. Currently, on Linux 2.6.32 and Linux 3.15.7, although it should work on
most \*nix systems; Mac OS X Yosemite 10.10, FreeBSD 10.0, and finally
Windows 7 (with the Cygwin port of GCC).
**Q. Can I use it on Windows without Cygwin?**
A. Yes, you can, Cygwin is only required to compile the static library if
you build it from source -- otherwise, a GNU C compatible compiler (like
GCC or Clang) is needed to compile your tests with the library.
Windows 7 (with the MinGW and Cygwin ports of GCC).
[online-docs]: http://criterion.readthedocs.org/
[pdf-docs]: http://readthedocs.org/projects/criterion/downloads/pdf/latest/

View file

@ -14,9 +14,10 @@ init:
-P mingw-gcc-core \
-P mingw-pthreads \
-P mingw-w32api \
-P mingw64-i686-gcc-core \
-P libtool \
-P make \
-P python \
-P python3 \
-P gettext-devel \
-P gettext \
-P expat \
@ -27,7 +28,6 @@ init:
-P git \
-P wget \
-P curl
- "%CYG_BASH% -lc 'echo $PATH'"
environment:
global:
@ -35,6 +35,8 @@ environment:
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin\var\cache\setup
CYG_BASH: C:\cygwin\bin\bash
COVERALLS_TOKEN:
secure: 5nuCg+faxFPeppoNNcSwVobswAVFUf8ut83vw8CX/4W2y0kZkGmwEfCUxSQWiQDU
cache:
- '%CYG_CACHE%'
@ -46,22 +48,27 @@ matrix:
platform:
- x86
- Any CPU
configuration: Release
install:
- 'set GCOV_PREFIX=%APPVEYOR_BUILD_FOLDER%'
- "%CYG_BASH% -lc 'cd $APPVEYOR_BUILD_FOLDER; ./autogen.sh'"
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; ./configure"'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; ./configure CC=i686-w64-mingw32-gcc CFLAGS=\"-g -O0\" --enable-gcov"'
build_script:
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make"'
before_test:
- "%CYG_BASH% -lc 'cd $APPVEYOR_BUILD_FOLDER; .ci/install-pip.sh'"
- "%CYG_BASH% -lc 'pip install cpp-coveralls'"
test_script:
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make check"'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER/samples; exec 0</dev/null; make check"'
after_test:
- '%CYG_BASH% -lc "cat $(find $APPVEYOR_BUILD_FOLDER/samples -iname \"*.log\") /dev/null'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; coveralls --gcov i686-w64-mingw32-gcov --exclude samples --exclude dependencies --gcov-options \"\-lp\" -b . -t $COVERALLS_TOKEN'
notifications:

View file

@ -1,3 +1,4 @@
#!/bin/sh
git submodule update --init --recursive
autopoint
autoreconf -i

View file

@ -1,6 +1,6 @@
AC_PREREQ([2.60])
AC_INIT([criterion], [1.1.0], [], [criterion], [franklinmathieu@gmail.com])
AC_INIT([criterion], [1.2.0], [], [criterion], [franklinmathieu@gmail.com])
AC_CONFIG_SRCDIR([src/runner.c])
LT_PREREQ([2.2.4])
@ -20,6 +20,16 @@ AC_PROG_LN_S
AC_PROG_MAKE_SET
AC_SUBST([LIBTOOL_DEPS])
AC_FUNC_FNMATCH
enable_rt_tests="no"
AC_CHECK_LIB([rt], [clock_gettime], [enable_rt_tests="yes"])
AM_CONDITIONAL([ENABLE_RT_TESTS], [test "x$enable_rt_tests" != "xno"])
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.18])
AC_ARG_ENABLE([gcov],
[AS_HELP_STRING([--enable-gcov],
[Compile the project with converage enabled])],
@ -31,7 +41,7 @@ AC_ARG_ENABLE([gcov],
[])
AC_CONFIG_HEADERS([src/config.h])
AC_CONFIG_FILES([Makefile samples/Makefile])
AC_CONFIG_FILES([Makefile samples/Makefile po/Makefile.in])
AC_CONFIG_SUBDIRS([dependencies/csptr])

2
dependencies/csptr vendored

@ -1 +1 @@
Subproject commit 15b825ffb8ffe7309bf524659eb900d6bb1d7c04
Subproject commit 4f3e63aca586939ed734f4e76c4f7f7f8c07d247

View file

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

View file

@ -14,7 +14,7 @@ Command line arguments
* ``-f or --fail-fast``: Exit after the first test failure.
* ``--ascii``: Don't use fancy unicode symbols or colors in the output.
* ``--pattern [PATTERN]``: Run tests whose string identifier matches
the given shell wildcard pattern (see dedicated section below).
the given shell wildcard pattern (see dedicated section below). (\*nix only)
* ``--no-early-exit``: The test workers shall not prematurely exit when done and
will properly return from the main, cleaning up their process space.
This is useful when tracking memory leaks with ``valgrind --tool=memcheck``.
@ -27,7 +27,8 @@ Shell Wildcard Pattern
----------------------
Patterns in criterion are matched against a test's string identifier with
``fnmatch``.
``fnmatch``. This feature is only available on \*nix systems where ``fnmatch``
is provided.
Special characters used in shell-style wildcard patterns are:
@ -61,4 +62,4 @@ Environment variables are alternatives to command line switches when set to 1.
* ``CRITERION_VERBOSITY_LEVEL``: Same as ``--verbose``. Sets the verbosity level
to its value.
* ``CRITERION_TEST_PATTERN``: Same as ``--pattern``. Sets the test pattern
to its value.
to its value. (\*nix only)

View file

@ -14,4 +14,5 @@ There are plenty of ways to fix that behaviour:
**Q. I'm having an issue with the library, what can I do ?**
A. Open a new issue on the `github issue tracker <https://github.com/Snaipe/Criterion/issues>`_,
and describe the problem you are experiencing.
and describe the problem you are experiencing, along with the platform you are
running criterion on.

View file

@ -17,6 +17,10 @@ A report hook can be declared using the ``ReportHook`` macro:
The macro takes a Phase parameter that indicates the phase at which the function
shall be run. Valid phases are described below.
**Note**: there are no guarantees regarding the order of execution of report hooks
on the same phase. In other words, all report hooks of a specific phase could
be executed in any order.
Testing Phases
--------------
@ -49,3 +53,18 @@ Valid types for each phases are:
* ``struct criterion_test_stats *`` for ``POST_TEST``, ``POST_FINI``, and ``TEST_CRASH``.
* ``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:
.. code-block:: c
#include <criterion/criterion.h>
#include <criterion/hooks.h>
ReportHook(PRE_TEST)() {
// not using the parameter
}
ReportHook(PRE_TEST)(struct criterion_test *test) {
// using the parameter
}

View file

@ -9,4 +9,5 @@ Criterion
starter
hooks
env
internal
faq

54
doc/internal.rst Normal file
View file

@ -0,0 +1,54 @@
Changing the internals
======================
Providing your own main
-----------------------
If you are not satisfied with the default CLI or environment variables, you
can define your own main function.
Configuring the test runner
~~~~~~~~~~~~~~~~~~~~~~~~~~~
You'd usually want to configure the test runner before calling it.
Configuration is done by setting fields in a global variable named
``criterion_options`` (include criterion/options.h).
Here is an exhaustive list of these fields:
=================== ================================== ==============================================================
Field Type Description
=================== ================================== ==============================================================
logging_threshold enum criterion_logging_level The logging level
------------------- ---------------------------------- --------------------------------------------------------------
output_provider struct criterion_output_provider * The output provider (see below)
------------------- ---------------------------------- --------------------------------------------------------------
no_early_exit bool True iff the test worker should exit early
------------------- ---------------------------------- --------------------------------------------------------------
always_succeed bool True iff criterion_run_all_tests should always returns 1
------------------- ---------------------------------- --------------------------------------------------------------
use_ascii bool True iff the outputs should use the ASCII charset
------------------- ---------------------------------- --------------------------------------------------------------
fail_fast bool True iff the test runner should abort after the first failure
------------------- ---------------------------------- --------------------------------------------------------------
pattern const char * The pattern of the tests that should be executed
=================== ================================== ==============================================================
Starting the test runner
~~~~~~~~~~~~~~~~~~~~~~~~
The test runner can be called with ``criterion_run_all_tests``. The function
returns 0 if one test or more failed, 1 otherwise.
Implementing your own output provider
-------------------------------------
In case you are not satisfied by the default output provider, you can implement
yours. To do so, simply set the ``output_provider`` option to your custom
output provider.
Each function contained in the structure is called during one of the standard
phase of the criterion runner.
For more insight on how to implement this, see other existing output providers
in ``src/log/``.

View file

@ -50,26 +50,52 @@ parameter, and an optional failure message:
On top of those, more assertions are available for common operations:
* ``{assert,expect}_not(Actual, Expected, [Message])``
* ``{assert,expect}_eq(Actual, Expected, [Message])``
* ``{assert,expect}_neq(Actual, Unexpected, [Message])``
* ``{assert,expect}_lt(Actual, Expected, [Message])``
* ``{assert,expect}_leq(Actual, Expected, [Message])``
* ``{assert,expect}_gt(Actual, Expected, [Message])``
* ``{assert,expect}_geq(Actual, Expected, [Message])``
* ``{assert,expect}_float_eq(Actual, Expected, Epsilon, [Message])``
* ``{assert,expect}_float_neq(Actual, Unexpected, Epsilon, [Message])``
* ``{assert,expect}_strings_eq(Actual, Expected, [Message])``
* ``{assert,expect}_strings_neq(Actual, Unexpected, [Message])``
* ``{assert,expect}_strings_lt(Actual, Expected, [Message])``
* ``{assert,expect}_strings_leq(Actual, Expected, [Message])``
* ``{assert,expect}_strings_gt(Actual, Expected, [Message])``
* ``{assert,expect}_strings_geq(Actual, Expected, [Message])``
* ``{assert,expect}_arrays_eq(Actual, Expected, Size, [Message])``
* ``{assert,expect}_arrays_neq(Actual, Unexpected, Size, [Message])``
* ``assert_null(Ptr, [Message])``: passes if Ptr is NULL.
* ``assert_eq(Actual, Expected, [Message])``: passes if Actual == Expected.
* ``assert_lt(Actual, Expected, [Message])``: passes if Actual < Expected.
* ``assert_leq(Actual, Expected, [Message])``: passes if Actual <= Expected.
* ``assert_gt(Actual, Expected, [Message])``: passes if Actual > Expected.
* ``assert_geq(Actual, Expected, [Message])``: passes if Actual >= Expected.
* ``assert_float_eq(Actual, Expected, Epsilon, [Message])``:
passes if Actual == Expected with an error of Epsilon.
* ``assert_arrays_eq(Actual, Expected, Size, [Message])``:
passes if all elements of Actual (from 0 to Size - 1) are equals to those
of Expected.
* ``assert_arrays_eq_cmp(Actual, Expected, Size, Cmp, [Message])``:
Same as ``arrays_eq`` but equality is defined by the result of the binary
Cmp function.
Equality and lexical comparison assertions are also available for strings:
* ``assert_strings_eq(Actual, Expected, [Message])``
* ``assert_strings_lt(Actual, Expected, [Message])``
* ``assert_strings_leq(Actual, Expected, [Message])``
* ``assert_strings_gt(Actual, Expected, [Message])``
* ``assert_strings_geq(Actual, Expected, [Message])``
And some assertions have a logical negative counterpart:
* ``assert_not(Condition, [Message])``
* ``assert_not_null(Ptr, [Message])``
* ``assert_neq(Actual, Unexpected, [Message])``
* ``assert_float_neq(Actual, Unexpected, Epsilon, [Message])``
* ``assert_strings_neq(Actual, Unexpected, [Message])``
* ``assert_arrays_neq(Actual, Unexpected, Size, [Message])``
* ``assert_arrays_neq_cmp(Actual, Unexpected, Size, Cmp, [Message])``
Of course, every ``assert`` has an ``expect`` counterpart.
Please note that ``arrays_(n)eq`` assertions should not be used on padded
structures -- please use ``arrays_(n)eq_cmp`` instead.
Configuring tests
-----------------
Tests may receive optional configuration parameters to alter their behaviour
or provide additional metadata.
Fixtures
--------
~~~~~~~~
Tests that need some setup and teardown can register functions that will
run before and after the test function:
@ -91,8 +117,13 @@ run before and after the test function:
// test contents
}
If a setup crashes, you will get a warning message, and the test will be aborted
and marked as a failure.
Is a teardown crashes, you will get a warning message, and the test will keep
its result.
Testing signals
---------------
~~~~~~~~~~~~~~~
If a test receives a signal, it will by default be marked as a failure.
You can, however, expect a test to only pass if a special kind of signal
@ -115,3 +146,47 @@ is received:
int *ptr = NULL;
*ptr = 42;
}
This feature will of course not work on Windows.
Configuration reference
~~~~~~~~~~~~~~~~~~~~~~~
Here is an exhaustive list of all possible configuration parameters you can
pass:
============= =============== ==============================================================
Parameter Type Description
============= =============== ==============================================================
.description const char * Adds a description. Cannot be ``NULL``.
------------- --------------- --------------------------------------------------------------
.init void (*)(void) Adds a setup function the be executed before the test.
------------- --------------- --------------------------------------------------------------
.fini void (*)(void) Adds a teardown function the be executed after the test.
------------- --------------- --------------------------------------------------------------
.disabled bool Disables the test.
------------- --------------- --------------------------------------------------------------
.signal int Expect the test to raise the specified signal.
============= =============== ==============================================================
Setting up suite-wise configuration
-----------------------------------
Tests under the same suite can have a suite-wise configuration -- this is done
using the ``TestSuite`` macro:
.. code-block:: c
#include <criterion/criterion.h>
TestSuite(suite_name, [params...]);
Test(suite_name, test_1) {
}
Test(suite_name, test_2) {
}
Configuration parameters are the same as above, but applied to the suite itself.
Suite fixtures are run *along with* test fixtures.

31
include/criterion/abort.h Normal file
View file

@ -0,0 +1,31 @@
/*
* 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_ABORT_H_
# define CRITERION_ABORT_H_
# include "common.h"
NORETURN void criterion_abort_test(void);
#endif /* !CRITERION_ABORT_H_ */

View file

@ -31,6 +31,7 @@
# include "stats.h"
# include "hooks.h"
# include "event.h"
# include "abort.h"
enum criterion_assert_kind {
NORMAL,
@ -60,7 +61,7 @@ struct criterion_assert_args {
}; \
send_event(ASSERT, &stat, sizeof (stat)); \
if (!passed && (Kind) == FATAL) \
return; \
criterion_abort_test(); \
} while (0)
// Common asserts
@ -110,6 +111,22 @@ struct criterion_assert_args {
# define assert_geq(...) assert_op_(>=, __VA_ARGS__, .sentinel_ = 0)
# define expect_geq(...) expect_op_(>=, __VA_ARGS__, .sentinel_ = 0)
# define assert_null_(Value, ...) \
assert_impl(FATAL, (Value) == NULL, __VA_ARGS__)
# define expect_null_(Value, ...) \
assert_impl(NORMAL, (Value) == NULL, __VA_ARGS__)
# define assert_null(...) assert_null_(__VA_ARGS__, .sentinel_ = 0)
# define expect_null(...) expect_null_(__VA_ARGS__, .sentinel_ = 0)
# define assert_not_null_(Value, ...) \
assert_impl(FATAL, (Value) != NULL, __VA_ARGS__)
# define expect_not_null_(Value, ...) \
assert_impl(NORMAL, (Value) != NULL, __VA_ARGS__)
# define assert_not_null(...) assert_not_null_(__VA_ARGS__, .sentinel_ = 0)
# define expect_not_null(...) expect_not_null_(__VA_ARGS__, .sentinel_ = 0)
// Floating-point asserts
# define assert_float_eq(...) \
@ -172,23 +189,82 @@ struct criterion_assert_args {
// Array asserts
# define assert_arrays_eq(...) \
assert_arrays_eq_(__VA_ARGS__, .sentinel = 0)
assert_arrays_eq_(__VA_ARGS__, .sentinel_ = 0)
# define expect_arrays_eq(...) \
expect_arrays_eq_(__VA_ARGS__, .sentinel = 0)
expect_arrays_eq_(__VA_ARGS__, .sentinel_ = 0)
# define assert_arrays_neq(...) \
assert_arrays_neq_(__VA_ARGS__, .sentinel = 0)
assert_arrays_neq_(__VA_ARGS__, .sentinel_ = 0)
# define expect_arrays_neq(...) \
expect_arrays_neq_(__VA_ARGS__, .sentinel = 0)
expect_arrays_neq_(__VA_ARGS__, .sentinel_ = 0)
# define assert_arrays_eq_(A, B, Size, ...) \
assert_impl(FATAL, !memcmp((A), (B), (Size)), __VA_ARGS__)
# define assert_arrays_eq_(A, B, Size, ...) \
assert_impl(FATAL, !memcmp((A), (B), (Size)), \
.default_msg = "Arrays are not equal.", \
__VA_ARGS__)
# define expect_arrays_eq_(A, B, Size, ...) \
assert_impl(NORMAL, !memcmp((A), (B), (Size)), __VA_ARGS__)
assert_impl(NORMAL, !memcmp((A), (B), (Size)), \
.default_msg = "Arrays are not equal.", \
__VA_ARGS__)
# define assert_arrays_neq_(A, B, Size, ...) \
assert_impl(FATAL, memcmp((A), (B), (Size)), __VA_ARGS__)
# define expect_arrays_neq_(A, B, Size, ...) \
assert_impl(NORMAL, memcmp((A), (B), (Size)), __VA_ARGS__)
# define assert_arrays_neq_(A, B, Size, ...) \
assert_impl(FATAL, memcmp((A), (B), (Size)), \
.default_msg = "Arrays are equal", \
__VA_ARGS__)
# define expect_arrays_neq_(A, B, Size, ...) \
assert_impl(NORMAL, memcmp((A), (B), (Size)), \
.default_msg = "Arrays are equal", \
__VA_ARGS__)
# ifdef __GNUC__
# define CRIT_ARR_COMPARE_(A, B, Size, Cmp, Result) \
__typeof__(&(A)[0]) first = (A); \
__typeof__(&(B)[0]) second = (B); \
int equals = 1; \
for (size_t i = 0, size = (Size); equals && i < size; ++i) \
equals = equals && !Cmp(first + i, second + i)
# define assert_arrays_eq_cmp_(A, B, Size, Cmp, ...) \
do { \
CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \
assert_impl(FATAL, equals, \
.default_msg = "Arrays are not equal", \
__VA_ARGS__); \
} while (0)
# define expect_arrays_eq_cmp_(A, B, Size, Cmp, ...) \
do { \
CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \
assert_impl(NORMAL, equals, \
.default_msg = "Arrays are not equal", \
__VA_ARGS__); \
} while (0)
# define assert_arrays_eq_cmp(...) \
assert_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0)
# define expect_arrays_eq_cmp(...) \
expect_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0)
# define assert_arrays_neq_cmp_(A, B, Size, Cmp, ...) \
do { \
CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \
assert_impl(FATAL, !equals, \
.default_msg = "Arrays not equal", \
__VA_ARGS__); \
} while (0)
# define expect_arrays_neq_cmp_(A, B, Size, Cmp, ...) \
do { \
CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \
assert_impl(NORMAL, equals, \
.default_msg = "Arrays not equal", \
__VA_ARGS__); \
} while (0)
# define assert_arrays_neq_cmp(...) \
assert_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0)
# define expect_arrays_neq_cmp(...) \
expect_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0)
# endif /* !__GNUC__ */
#endif /* !CRITERION_ASSERT_H_ */

View file

@ -56,6 +56,7 @@
Type *const SECTION_END(Name) = &SECTION_END_(Name)
# define UNUSED __attribute__((unused))
# define NORETURN __attribute__((noreturn))
# ifdef _WIN32
# define SIZE_T_FORMAT "%Iu"

View file

@ -25,8 +25,9 @@
# define CRITERION_EVENT_H_
# include <stddef.h>
# include <stdio.h>
extern int EVENT_PIPE;
extern FILE *g_event_pipe;
void send_event(int kind, void *data, size_t size);

View file

@ -25,6 +25,7 @@
# define CRITERION_LOGGING_H_
# include <stdbool.h>
# include <stdarg.h>
# include "common.h"
# include "ordered-set.h"
# include "stats.h"
@ -34,23 +35,73 @@ enum criterion_logging_level {
CRITERION_IMPORTANT,
};
enum criterion_logging_prefix {
CRITERION_LOGGING_PREFIX_DASHES,
CRITERION_LOGGING_PREFIX_EQUALS,
CRITERION_LOGGING_PREFIX_RUN,
CRITERION_LOGGING_PREFIX_SKIP,
CRITERION_LOGGING_PREFIX_PASS,
CRITERION_LOGGING_PREFIX_FAIL,
};
struct criterion_prefix_data {
const char *prefix;
const char *color;
};
# ifdef CRITERION_LOGGING_COLORS
# define CRIT_COLOR_NORMALIZE(Str) (criterion_options.use_ascii ? "" : Str)
# define CRIT_FG_BOLD "\e[0;1m"
# define CRIT_FG_RED "\e[0;31m"
# define CRIT_FG_GREEN "\e[0;32m"
# define CRIT_FG_GOLD "\e[0;33m"
# define CRIT_FG_BLUE "\e[0;34m"
# define CRIT_RESET "\e[0m"
# define FG_BOLD CRIT_COLOR_NORMALIZE(CRIT_FG_BOLD)
# define FG_RED CRIT_COLOR_NORMALIZE(CRIT_FG_RED)
# define FG_GREEN CRIT_COLOR_NORMALIZE(CRIT_FG_GREEN)
# define FG_GOLD CRIT_COLOR_NORMALIZE(CRIT_FG_GOLD)
# define FG_BLUE CRIT_COLOR_NORMALIZE(CRIT_FG_BLUE)
# define RESET CRIT_COLOR_NORMALIZE(CRIT_RESET)
# endif
extern const struct criterion_prefix_data g_criterion_logging_prefixes[];
# define CRITERION_PREFIX_DASHES (&g_criterion_logging_prefixes[CRITERION_LOGGING_PREFIX_DASHES])
# define CRITERION_PREFIX_EQUALS (&g_criterion_logging_prefixes[CRITERION_LOGGING_PREFIX_EQUALS])
# define CRITERION_PREFIX_RUN (&g_criterion_logging_prefixes[CRITERION_LOGGING_PREFIX_RUN ])
# define CRITERION_PREFIX_SKIP (&g_criterion_logging_prefixes[CRITERION_LOGGING_PREFIX_SKIP ])
# define CRITERION_PREFIX_PASS (&g_criterion_logging_prefixes[CRITERION_LOGGING_PREFIX_PASS ])
# define CRITERION_PREFIX_FAIL (&g_criterion_logging_prefixes[CRITERION_LOGGING_PREFIX_FAIL ])
void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args);
FORMAT(printf, 3, 4)
void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...);
FORMAT(printf, 2, 3)
void criterion_log(enum criterion_logging_level level, const char *msg, ...);
# define criterion_info(...) criterion_log(CRITERION_INFO, __VA_ARGS__)
# define criterion_important(...) criterion_log(CRITERION_IMPORTANT, __VA_ARGS__)
# define criterion_pinfo(...) criterion_plog(CRITERION_INFO, __VA_ARGS__)
# define criterion_pimportant(...) criterion_plog(CRITERION_IMPORTANT, __VA_ARGS__)
struct criterion_output_provider {
void (*log_pre_all )(struct criterion_test_set *set);
void (*log_pre_suite )(struct criterion_suite_set *set);
void (*log_pre_init )(struct criterion_test *test);
void (*log_pre_test )(struct criterion_test *test);
void (*log_assert )(struct criterion_assert_stats *stats);
void (*log_test_crash)(struct criterion_test_stats *stats);
void (*log_post_test )(struct criterion_test_stats *stats);
void (*log_post_fini )(struct criterion_test_stats *stats);
void (*log_post_suite)(struct criterion_suite_stats *stats);
void (*log_post_all )(struct criterion_global_stats *stats);
void (*log_pre_all )(struct criterion_test_set *set);
void (*log_pre_suite )(struct criterion_suite_set *set);
void (*log_pre_init )(struct criterion_test *test);
void (*log_pre_test )(struct criterion_test *test);
void (*log_assert )(struct criterion_assert_stats *stats);
void (*log_test_crash )(struct criterion_test_stats *stats);
void (*log_other_crash)(struct criterion_test_stats *stats);
void (*log_post_test )(struct criterion_test_stats *stats);
void (*log_post_fini )(struct criterion_test_stats *stats);
void (*log_post_suite )(struct criterion_suite_stats *stats);
void (*log_post_all )(struct criterion_global_stats *stats);
};
extern struct criterion_output_provider normal_logging;

View file

@ -26,10 +26,12 @@
# include "types.h"
typedef int (*f_criterion_cmp)(void *, void *);
struct criterion_ordered_set {
struct criterion_ordered_set_node *first;
size_t size;
int (*const cmp)(void *, void *);
f_criterion_cmp cmp;
void (*const dtor)(void *, void *);
};
@ -48,12 +50,16 @@ struct criterion_test_set {
size_t tests;
};
struct criterion_ordered_set *new_ordered_set(int (*cmp)(void *, void *), void (*dtor)(void *, void *));
void *insert_ordered_set(struct criterion_ordered_set *l, void *ptr, size_t size);
struct criterion_ordered_set *new_ordered_set(f_criterion_cmp cmp,
void (*dtor)(void *, void *));
# define FOREACH_SET(Elt, Set) \
for (struct criterion_ordered_set_node *n = Set->first; n; n = n->next) \
for (int cond = 1; cond;) \
void *insert_ordered_set(struct criterion_ordered_set *l,
void *ptr,
size_t size);
# define FOREACH_SET(Elt, Set) \
for (struct criterion_ordered_set_node *n = Set->first; n; n = n->next) \
for (int cond = 1; cond;) \
for (Elt = (void*) n->data; cond && (cond = 0, 1);)
#endif /* !CRITERION_ORDERED_SET_H_ */

View file

@ -56,6 +56,7 @@ struct criterion_suite_stats {
struct criterion_test_stats *tests;
size_t nb_tests;
size_t nb_asserts;
size_t tests_skipped;
size_t tests_failed;
size_t tests_crashed;
size_t tests_passed;
@ -70,6 +71,7 @@ struct criterion_global_stats {
size_t nb_suites;
size_t nb_tests;
size_t nb_asserts;
size_t tests_skipped;
size_t tests_failed;
size_t tests_crashed;
size_t tests_passed;

View file

@ -29,9 +29,9 @@
struct criterion_test_extra_data {
int sentinel_;
const char *const identifier_;
const char *const file_;
const unsigned line_;
const char *identifier_;
const char *file_;
unsigned line_;
void (*init)(void);
void (*fini)(void);
int signal;
@ -44,12 +44,14 @@ struct criterion_test {
const char *name;
const char *category;
void (*test)(void);
struct criterion_test_extra_data *const data;
struct criterion_test_extra_data *data;
};
struct criterion_suite {
const char *name;
struct criterion_test_extra_data *const data;
struct criterion_test_extra_data *data;
};
typedef void (*f_worker_func)(struct criterion_test *, struct criterion_suite *);
#endif /* !CRITERION_TYPES_H_ */

1
po/LINGUAS Normal file
View file

@ -0,0 +1 @@
fr

78
po/Makevars Normal file
View file

@ -0,0 +1,78 @@
# Makefile variables for PO directory in any package using GNU gettext.
# Usually the message domain is the same as the package name.
DOMAIN = $(PACKAGE)
# These two variables depend on the location of this directory.
subdir = po
top_builddir = ..
# These options get passed to xgettext.
XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
# This is the copyright holder that gets inserted into the header of the
# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
# package. (Note that the msgstr strings, extracted from the package's
# sources, belong to the copyright holder of the package.) Translators are
# expected to transfer the copyright for their translations to this person
# or entity, or to disclaim their copyright. The empty string stands for
# the public domain; in this case the translators are expected to disclaim
# their copyright.
COPYRIGHT_HOLDER = Franklin "Snaipe" Mathieu
# This tells whether or not to prepend "GNU " prefix to the package
# name that gets inserted into the header of the $(DOMAIN).pot file.
# Possible values are "yes", "no", or empty. If it is empty, try to
# detect it automatically by scanning the files in $(top_srcdir) for
# "GNU packagename" string.
PACKAGE_GNU = no
# This is the email address or URL to which the translators shall report
# bugs in the untranslated strings:
# - Strings which are not entire sentences, see the maintainer guidelines
# in the GNU gettext documentation, section 'Preparing Strings'.
# - Strings which use unclear terms or require additional context to be
# understood.
# - Strings which make invalid assumptions about notation of date, time or
# money.
# - Pluralisation problems.
# - Incorrect English spelling.
# - Incorrect formatting.
# It can be your email address, or a mailing list address where translators
# can write to without being subscribed, or the URL of a web page through
# which the translators can contact you.
MSGID_BUGS_ADDRESS =
# This is the list of locale categories, beyond LC_MESSAGES, for which the
# message catalogs shall be used. It is usually empty.
EXTRA_LOCALE_CATEGORIES =
# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
# context. Possible values are "yes" and "no". Set this to yes if the
# package uses functions taking also a message context, like pgettext(), or
# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
USE_MSGCTXT = no
# These options get passed to msgmerge.
# Useful options are in particular:
# --previous to keep previous msgids of translated messages,
# --quiet to reduce the verbosity.
MSGMERGE_OPTIONS =
# These options get passed to msginit.
# If you want to disable line wrapping when writing PO files, add
# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and
# MSGINIT_OPTIONS.
MSGINIT_OPTIONS =
# This tells whether or not to regenerate a PO file when $(DOMAIN).pot
# has changed. Possible values are "yes" and "no". Set this to no if
# the POT file is checked in the repository and the version control
# program ignores timestamps.
PO_DEPENDS_ON_POT = yes
# This tells whether or not to forcibly update $(DOMAIN).pot and
# regenerate PO files on "make dist". Possible values are "yes" and
# "no". Set this to no if the POT file and PO files are maintained
# externally.
DIST_DEPENDS_ON_UPDATE_PO = yes

2
po/POTFILES.in Normal file
View file

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

77
po/fr.po Normal file
View file

@ -0,0 +1,77 @@
# French translations for criterion package
# Traductions françaises du paquet criterion.
# Copyright (C) 2015 Franklin "Snaipe" Mathieu
# This file is distributed under the same license as the criterion package.
# <franklinmathieu@gmail.com>, 2015.
#
msgid ""
msgstr ""
"Project-Id-Version: criterion 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-10 23:08+0200\n"
"PO-Revision-Date: 2015-04-03 17:58+0200\n"
"Last-Translator: <franklinmathieu@gmail.com>\n"
"Language-Team: French\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/log/normal.c:38
#, c-format
msgid "Criterion v%s\n"
msgstr "Criterion v%s\n"
#: src/log/normal.c:42
#, c-format
msgid "%1$s::%2$s\n"
msgstr "%1$s::%2$s\n"
#: src/log/normal.c:47 src/log/normal.c:121
#, c-format
msgid " %s\n"
msgstr " %s\n"
#: src/log/normal.c:76
#, c-format
msgid "%1$s::%2$s: Test is disabled\n"
msgstr "%1$s::%2$s: Le test est désactivé\n"
#: src/log/normal.c:77
#, c-format
msgid "%1$s::%2$s: Suite is disabled\n"
msgstr "%1$s::%2$s: La suite est désactivée\n"
#: src/log/normal.c:94
#, c-format
msgid ""
"%1$sSynthesis: Tested: %2$s%3$lu%4$s | Passing: %5$s%6$lu%7$s | Failing: %8$s"
"%9$lu%10$s | Crashing: %11$s%12$lu%13$s %14$s\n"
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/log/normal.c:115
#, c-format
msgid "%1$s%2$s%3$s:%4$s%5$d%6$s: Assertion failed: %7$s\n"
msgstr "%1$s%2$s%3$s:%4$s%5$d%6$s: Échec d'assertion: %7$s\n"
#: src/log/normal.c:128
#, c-format
msgid "%1$s%2$s%3$s:%4$s%5$u%6$s: Unexpected signal caught below this line!\n"
msgstr ""
"%1$s%2$s%3$s:%4$s%5$u%6$s: Un signal inattendu a été reçu après cette "
"ligne!\n"
#: src/log/normal.c:132
#, c-format
msgid "%1$s::%2$s: CRASH!\n"
msgstr "%1$s::%2$s: PLANTAGE!\n"
#: src/log/normal.c:139
#, c-format
msgid "%1$sWarning! This test crashed during its setup or teardown.%2$s\n"
msgstr ""
"%1$sAttention! Ce test a planté pendant son initialisation ou sa "
"finalisation.%2$s\n"

View file

@ -7,6 +7,7 @@ BIN_TESTS = \
more-suites \
long-messages \
description \
other-crashes \
simple
TESTS_ENVIRONMENT = CRITERION_ALWAYS_SUCCEED=1
@ -15,6 +16,11 @@ check_PROGRAMS := $(BIN_TESTS)
CFLAGS = -I$(top_srcdir)/include/ -std=c99 -Wall -Wextra -pedantic
LDADD = -L$(top_srcdir)/ -lcriterion
if ENABLE_RT_TESTS
BIN_TESTS += with-time
with_time_LDADD = $(LDADD) -lrt
endif
SCRIPT_TESTS = tests/tap_test.sh \
tests/early_exit.sh \
tests/verbose.sh \

View file

@ -50,3 +50,31 @@ Test(asserts, float) {
assert_neq(0.1 * 0.1, 0.01);
assert_float_eq(0.1 * 0.1, 0.01, 0.001);
}
struct dummy_struct {
char a;
size_t b;
};
int eq_dummy(struct dummy_struct *a, struct dummy_struct *b) {
return a->a != b->a || a->b != b->b;
}
Test(asserts, array) {
int arr1[] = {1, 2, 3, 4};
int arr2[] = {4, 3, 2, 1};
struct dummy_struct s1[] = {{4, 2}, {2, 4}};
struct dummy_struct s2[2];
memset(s2, 0xFF, sizeof(s2));
s2[0].a = 4;
s2[0].b = 2;
s2[1].a = 2;
s2[1].b = 4;
assert_arrays_eq(arr1, arr1, 4);
assert_arrays_neq(arr1, arr2, 4);
assert_arrays_neq(s1, s2, 2);
assert_arrays_eq_cmp(s1, s2, 2, eq_dummy);
}

View file

@ -3,3 +3,6 @@
Test(misc, failing, .description = "Just a failing test") {
assert(0);
}
Test(misc, skipped, .description = "This one is skipped", .disabled = true) {
}

View file

@ -1,10 +1,12 @@
#include <criterion/criterion.h>
void setup_suite(void) {
// setup suite
}
TestSuite(suite1, .init = setup_suite);
void teardown_suite(void) {
}
TestSuite(suite1, .init = setup_suite, .fini = teardown_suite);
Test(suite1, test) {
assert(1);

14
samples/other-crashes.c Normal file
View file

@ -0,0 +1,14 @@
#include <criterion/criterion.h>
void crash(void) {
int *i = NULL;
*i = 42;
}
Test(misc, setup_crash, .init = crash) {
assert(true);
}
Test(misc, teardown_crash, .fini = crash) {
assert(true);
}

5
samples/with-time.c Normal file
View file

@ -0,0 +1,5 @@
#include <criterion/criterion.h>
Test(samples, timed) {
assert(0);
}

35
src/abort.c Normal file
View file

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

31
src/abort.h Normal file
View file

@ -0,0 +1,31 @@
/*
* 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 ABORT_H_
# define ABORT_H_
# include <criterion/abort.h>
int setup_abort_test(void);
#endif /* !ABORT_H_ */

View file

@ -22,39 +22,44 @@
* THE SOFTWARE.
*/
#include <unistd.h>
#include <stdio.h>
#include <csptr/smart_ptr.h>
#include "criterion/stats.h"
#include "criterion/common.h"
#include "criterion/hooks.h"
#include "event.h"
int EVENT_PIPE = -1;
FILE *g_event_pipe = NULL;
void destroy_event(void *ptr, UNUSED void *meta) {
struct event *ev = ptr;
free(ev->data);
}
struct event *read_event(int fd) {
struct event *read_event(FILE *f) {
unsigned kind;
if (read(fd, &kind, sizeof (unsigned)) < (ssize_t) sizeof (unsigned))
if (fread(&kind, sizeof (unsigned), 1, f) == 0)
return NULL;
switch (kind) {
case ASSERT: {
const size_t assert_size = sizeof (struct criterion_assert_stats);
unsigned char *buf = malloc(assert_size);
if (read(fd, buf, assert_size) < (ssize_t) assert_size)
if (fread(buf, assert_size, 1, f) == 0)
return NULL;
return unique_ptr(struct event, { .kind = kind, .data = buf }, destroy_event);
return unique_ptr(struct event,
.value = { .kind = kind, .data = buf },
.dtor = destroy_event);
}
case POST_TEST: {
double *elapsed_time = malloc(sizeof (double));
if (read(fd, elapsed_time, sizeof (double)) < (ssize_t) sizeof (double))
if (fread(elapsed_time, sizeof (double), 1, f) == 0)
return NULL;
return unique_ptr(struct event, { .kind = kind, .data = elapsed_time }, destroy_event);
return unique_ptr(struct event,
.value = { .kind = kind, .data = elapsed_time },
.dtor = destroy_event);
}
default:
return unique_ptr(struct event, { .kind = kind, .data = NULL });
@ -65,5 +70,6 @@ void send_event(int kind, void *data, size_t size) {
unsigned char buf[sizeof (int) + size];
memcpy(buf, &kind, sizeof (int));
memcpy(buf + sizeof (int), data, size);
write(EVENT_PIPE, buf, sizeof (int) + size);
if (fwrite(buf, sizeof (int) + size, 1, g_event_pipe) == 0)
abort();
}

View file

@ -31,6 +31,6 @@ struct event {
void *data;
};
struct event *read_event(int fd);
struct event *read_event(FILE *f);
#endif /* !EVENT_H_ */

8
src/i18n.c Normal file
View file

@ -0,0 +1,8 @@
#include "i18n.h"
#if ENABLE_NLS
__attribute__ ((constructor))
void init_i18n(void) {
bindtextdomain (PACKAGE, LOCALEDIR);
}
#endif

16
src/i18n.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef I18N_H_
# define I18N_H_
# include "config.h"
# if !ENABLE_NLS
# define _(String) String
# define _s(String, Plural, Quantity) ((Quantity) == 1 ? String : Plural)
# else
# include <libintl.h>
# define _(String) dgettext(PACKAGE, String)
# define _s(String, Plural, Quantity) \
dngettext(PACKAGE, String, Plural, (Quantity))
# endif
#endif /* !I18N_H_ */

View file

@ -21,18 +21,54 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#define CRITERION_LOGGING_COLORS
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "criterion/logging.h"
#include "criterion/options.h"
#include "i18n.h"
void criterion_log(enum criterion_logging_level level, const char *msg, ...) {
#define LOG_FORMAT "[%1$s%2$s%3$s] %4$s"
const struct criterion_prefix_data g_criterion_logging_prefixes[] = {
[CRITERION_LOGGING_PREFIX_DASHES] = { "----", CRIT_FG_BLUE },
[CRITERION_LOGGING_PREFIX_EQUALS] = { "====", CRIT_FG_BLUE },
[CRITERION_LOGGING_PREFIX_RUN] = { "RUN ", CRIT_FG_BLUE },
[CRITERION_LOGGING_PREFIX_SKIP] = { "SKIP", CRIT_FG_GOLD },
[CRITERION_LOGGING_PREFIX_PASS] = { "PASS", CRIT_FG_GREEN },
[CRITERION_LOGGING_PREFIX_FAIL] = { "FAIL", CRIT_FG_RED },
{ NULL }
};
void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...) {
va_list args;
if (level < criterion_options.logging_threshold)
return;
char formatted_msg[1024];
va_start(args, msg);
vfprintf(stderr, msg, args);
vsnprintf(formatted_msg, sizeof formatted_msg, msg, args);
va_end(args);
fprintf(stderr, _(LOG_FORMAT),
CRIT_COLOR_NORMALIZE(prefix->color),
prefix->prefix,
RESET,
formatted_msg);
}
void criterion_log(enum criterion_logging_level level, const char *msg, ...) {
va_list args;
va_start(args, msg);
criterion_vlog(level, msg, args);
va_end(args);
}
void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args) {
if (level < criterion_options.logging_threshold)
return;
vfprintf(stderr, msg, args);
}

View file

@ -22,6 +22,7 @@
* THE SOFTWARE.
*/
#define _GNU_SOURCE
#define CRITERION_LOGGING_COLORS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -31,128 +32,131 @@
#include "criterion/ordered-set.h"
#include "timer.h"
#include "config.h"
#define NORMALIZE(Str) (criterion_options.use_ascii ? "" : Str)
#define FG_BOLD NORMALIZE("\e[0;1m")
#define FG_RED NORMALIZE("\e[0;31m")
#define FG_GREEN NORMALIZE("\e[0;32m")
#define FG_GOLD NORMALIZE("\e[0;33m")
#define FG_BLUE NORMALIZE("\e[0;34m")
#define RESET NORMALIZE("\e[0m")
#include "i18n.h"
void normal_log_pre_all(UNUSED struct criterion_test_set *set) {
criterion_info("[%s====%s] Criterion v%s\n", FG_BLUE, RESET, VERSION);
criterion_pinfo(CRITERION_PREFIX_DASHES, _("Criterion v%s\n"), VERSION);
}
void normal_log_pre_init(struct criterion_test *test) {
criterion_info("[%sRUN%s ] %s::%s\n", FG_BLUE, RESET, test->category, test->name);
criterion_pinfo(CRITERION_PREFIX_RUN, _("%1$s::%2$s\n"),
test->category,
test->name);
if (test->data->description)
criterion_info("[%s----%s] %s\n", FG_BLUE, RESET, test->data->description);
criterion_pinfo(CRITERION_PREFIX_RUN, _(" %s\n"),
test->data->description);
}
void normal_log_post_test(struct criterion_test_stats *stats) {
const char *format = can_measure_time() ? "%s::%s: (%3.2fs)\n" : "%s::%s\n";
const enum criterion_logging_level level = stats->failed ? CRITERION_IMPORTANT
: CRITERION_INFO;
const char *color = stats->failed ? FG_RED : FG_GREEN;
const char *format = can_measure_time() ? "%1$s::%2$s: (%3$3.2fs)\n"
: "%1$s::%2$s\n";
criterion_log(level, "[%s%s%s] ", color, stats->failed ? "FAIL" : "PASS", RESET);
criterion_log(level, format,
const enum criterion_logging_level level
= stats->failed ? CRITERION_IMPORTANT : CRITERION_INFO;
const struct criterion_prefix_data *prefix
= stats->failed ? CRITERION_PREFIX_FAIL : CRITERION_PREFIX_PASS;
criterion_plog(level, prefix, _(format),
stats->test->category,
stats->test->name,
stats->elapsed_time);
}
__attribute__((always_inline))
static inline bool is_disabled(struct criterion_test *t, struct criterion_suite *s) {
static inline bool is_disabled(struct criterion_test *t,
struct criterion_suite *s) {
return t->data->disabled || (s->data && s->data->disabled);
}
void normal_log_post_suite(struct criterion_suite_stats *stats) {
for (struct criterion_test_stats *ts = stats->tests; ts; ts = ts->next) {
if (is_disabled(ts->test, stats->suite)) {
criterion_info("[%sSKIP%s] %s::%s: %s is disabled\n",
FG_GOLD,
RESET,
const char *format = ts->test->data->disabled
? _("%1$s::%2$s: Test is disabled\n")
: _("%1$s::%2$s: Suite is disabled\n");
criterion_pinfo(CRITERION_PREFIX_SKIP, format,
ts->test->category,
ts->test->name,
ts->test->data->disabled ? "test" : "suite");
ts->test->name);
if (ts->test->data->description)
criterion_info("[%s----%s] %s\n", FG_BLUE, RESET, ts->test->data->description);
criterion_pinfo(CRITERION_PREFIX_DASHES, " %s\n",
ts->test->data->description);
}
}
}
void normal_log_post_all(struct criterion_global_stats *stats) {
criterion_important("[%s====%s] ", FG_BLUE, RESET);
criterion_important("%sSynthesis: " SIZE_T_FORMAT " test%s run. " SIZE_T_FORMAT " passed, " SIZE_T_FORMAT " failed (with " SIZE_T_FORMAT " crash%s)%s\n",
size_t tested = stats->nb_tests - stats->tests_skipped;
criterion_pimportant(CRITERION_PREFIX_EQUALS,
_("%1$sSynthesis: Tested: %2$s%3$lu%4$s "
"| Passing: %5$s%6$lu%7$s "
"| Failing: %8$s%9$lu%10$s "
"| Crashing: %11$s%12$lu%13$s "
"%14$s\n"),
FG_BOLD,
stats->nb_tests,
stats->nb_tests == 1 ? " was" : "s were",
stats->tests_passed,
stats->tests_failed,
stats->tests_crashed,
stats->tests_crashed == 1 ? "" : "es",
FG_BLUE, (unsigned long) tested, FG_BOLD,
FG_GREEN, (unsigned long) stats->tests_passed, FG_BOLD,
FG_RED, (unsigned long) stats->tests_failed, FG_BOLD,
FG_RED, (unsigned long) stats->tests_crashed, FG_BOLD,
RESET);
}
void normal_log_assert(struct criterion_assert_stats *stats) {
if (!stats->passed) {
char *dup = strdup(*stats->message ? stats->message : stats->condition), *saveptr = NULL;
char *line = strtok_r(dup, "\n", &saveptr);
char *dup = strdup(*stats->message ? stats->message
: stats->condition);
char *saveptr = NULL;
char *line = strtok_r(dup, "\n", &saveptr);
criterion_important("[%s----%s] ", FG_BLUE, RESET);
criterion_important("%s%s%s:%s%d%s: Assertion failed: %s\n",
FG_BOLD,
stats->file,
RESET,
FG_RED,
stats->line,
RESET,
criterion_pimportant(CRITERION_PREFIX_DASHES,
_("%1$s%2$s%3$s:%4$s%5$d%6$s: Assertion failed: %7$s\n"),
FG_BOLD, stats->file, RESET,
FG_RED, stats->line, RESET,
line);
while ((line = strtok_r(NULL, "\n", &saveptr)))
criterion_important("[%s----%s] %s\n", FG_BLUE, RESET, line);
criterion_pimportant(CRITERION_PREFIX_DASHES, _(" %s\n"), line);
free(dup);
}
}
void normal_log_test_crash(struct criterion_test_stats *stats) {
criterion_important("[%s----%s] ", FG_BLUE, RESET);
criterion_important("%s%s%s:%s%u%s: Unexpected signal caught below this line!\n",
FG_BOLD,
stats->file,
RESET,
FG_RED,
stats->progress,
RESET);
criterion_important("[%sFAIL%s] %s::%s: CRASH!\n",
FG_RED,
RESET,
criterion_pimportant(CRITERION_PREFIX_DASHES,
_("%1$s%2$s%3$s:%4$s%5$u%6$s: "
"Unexpected signal caught below this line!\n"),
FG_BOLD, stats->file, RESET,
FG_RED, stats->progress, RESET);
criterion_pimportant(CRITERION_PREFIX_FAIL, _("%1$s::%2$s: CRASH!\n"),
stats->test->category,
stats->test->name);
}
void normal_log_other_crash(UNUSED struct criterion_test_stats *stats) {
criterion_pimportant(CRITERION_PREFIX_DASHES,
_("%1$sWarning! This test crashed during its setup or teardown.%2$s\n"),
FG_BOLD, RESET);
}
void normal_log_pre_suite(struct criterion_suite_set *set) {
criterion_info("[%s====%s] ", FG_BLUE, RESET);
criterion_info("Running %s" SIZE_T_FORMAT "%s test%s from %s%s%s:\n",
FG_BLUE,
set->tests->size,
RESET,
set->tests->size == 1 ? "" : "s",
FG_GOLD,
set->suite.name,
RESET);
criterion_pinfo(CRITERION_PREFIX_EQUALS,
_s("Running %1$s%2$lu%3$s test from %4$s%5$s%6$s:\n",
"Running %1$s%2$lu%3$s tests from %4$s%5$s%6$s:\n",
set->tests->size),
FG_BLUE, (unsigned long) set->tests->size, RESET,
FG_GOLD, set->suite.name, RESET);
}
struct criterion_output_provider normal_logging = {
.log_pre_all = normal_log_pre_all,
.log_pre_init = normal_log_pre_init,
.log_pre_suite = normal_log_pre_suite,
.log_assert = normal_log_assert,
.log_test_crash = normal_log_test_crash,
.log_post_test = normal_log_post_test,
.log_post_suite = normal_log_post_suite,
.log_post_all = normal_log_post_all,
.log_pre_all = normal_log_pre_all,
.log_pre_init = normal_log_pre_init,
.log_pre_suite = normal_log_pre_suite,
.log_assert = normal_log_assert,
.log_test_crash = normal_log_test_crash,
.log_other_crash = normal_log_other_crash,
.log_post_test = normal_log_post_test,
.log_post_suite = normal_log_post_suite,
.log_post_all = normal_log_post_all,
};

View file

@ -26,13 +26,26 @@
#include <criterion/options.h>
#include <criterion/ordered-set.h>
#include <stdio.h>
#include <locale.h>
#include <getopt.h>
#include <csptr/smart_ptr.h>
#include "runner.h"
#include "config.h"
#if ENABLE_NLS
# include <libintl.h>
#endif
# define VERSION_MSG "Tests compiled with Criterion v" VERSION "\n"
#ifdef HAVE_FNMATCH
# define PATTERN_USAGE \
" --pattern [PATTERN]: run tests matching the " \
"given pattern\n"
#else
# define PATTERN_USAGE
#endif
# define USAGE \
VERSION_MSG "\n" \
"usage: %s OPTIONS\n" \
@ -44,8 +57,7 @@
" -f or --fail-fast: exit after the first failure\n" \
" --ascii: don't use fancy unicode symbols " \
"or colors in the output\n" \
" --pattern [PATTERN]: run tests matching the " \
"given pattern\n" \
PATTERN_USAGE \
" --tap: enables TAP formatting\n" \
" --always-succeed: always exit with 0\n" \
" --no-early-exit: do not exit the test worker " \
@ -112,21 +124,31 @@ int main(int argc, char *argv[]) {
{"list", no_argument, 0, 'l'},
{"ascii", no_argument, 0, 'k'},
{"fail-fast", no_argument, 0, 'f'},
#ifdef HAVE_FNMATCH
{"pattern", required_argument, 0, 'p'},
#endif
{"always-succeed", no_argument, 0, 'y'},
{"no-early-exit", no_argument, 0, 'z'},
{0, 0, 0, 0 }
};
criterion_options = (struct criterion_options) {
.always_succeed = !strcmp("1", getenv("CRITERION_ALWAYS_SUCCEED") ?: "0"),
.no_early_exit = !strcmp("1", getenv("CRITERION_NO_EARLY_EXIT") ?: "0"),
.fail_fast = !strcmp("1", getenv("CRITERION_FAIL_FAST") ?: "0"),
.use_ascii = !strcmp("1", getenv("CRITERION_USE_ASCII") ?: "0"),
.logging_threshold = atoi(getenv("CRITERION_VERBOSITY_LEVEL") ?: "2"),
.pattern = getenv("CRITERION_TEST_PATTERN"),
.output_provider = NORMAL_LOGGING,
};
bool use_ascii = !strcmp("1", getenv("CRITERION_USE_ASCII") ?: "0")
|| !strcmp("dumb", getenv("TERM") ?: "dumb");
setlocale(LC_ALL, "");
#if ENABLE_NLS
textdomain (PACKAGE "-test");
#endif
struct criterion_options *opt = &criterion_options;
opt->always_succeed = !strcmp("1", getenv("CRITERION_ALWAYS_SUCCEED") ?: "0");
opt->no_early_exit = !strcmp("1", getenv("CRITERION_NO_EARLY_EXIT") ?: "0");
opt->fail_fast = !strcmp("1", getenv("CRITERION_FAIL_FAST") ?: "0");
opt->use_ascii = use_ascii;
opt->logging_threshold = atoi(getenv("CRITERION_VERBOSITY_LEVEL") ?: "2");
#ifdef HAVE_FNMATCH
opt->pattern = getenv("CRITERION_TEST_PATTERN");
#endif
bool use_tap = !strcmp("1", getenv("CRITERION_ENABLE_TAP") ?: "0");
@ -140,7 +162,9 @@ int main(int argc, char *argv[]) {
case 'z': criterion_options.no_early_exit = true; break;
case 'k': criterion_options.use_ascii = true; break;
case 'f': criterion_options.fail_fast = true; break;
#ifdef HAVE_FNMATCH
case 'p': criterion_options.pattern = optarg; break;
#endif
case 't': use_tap = true; break;
case 'l': do_list_tests = true; break;
case 'v': do_print_version = true; break;

View file

@ -23,4 +23,7 @@
*/
# include "criterion/options.h"
struct criterion_options criterion_options = { .logging_threshold = CRITERION_IMPORTANT };
struct criterion_options criterion_options = {
.logging_threshold = CRITERION_IMPORTANT,
.output_provider = &normal_logging,
};

View file

@ -39,12 +39,17 @@ static void destroy_ordered_set_node(void *ptr, void *meta) {
sfree(((struct criterion_ordered_set_node *) ptr)->next);
}
struct criterion_ordered_set *new_ordered_set(int (*cmp)(void *, void *), f_destructor dtor) {
struct criterion_ordered_set *new_ordered_set(f_criterion_cmp cmp,
f_destructor dtor) {
return unique_ptr(struct criterion_ordered_set,
{ .cmp = cmp, .dtor = dtor }, destroy_ordered_set);
.value = { .cmp = cmp, .dtor = dtor },
.dtor = destroy_ordered_set);
}
void *insert_ordered_set(struct criterion_ordered_set *l, void *ptr, size_t size) {
void *insert_ordered_set(struct criterion_ordered_set *l,
void *ptr,
size_t size) {
int cmp;
struct criterion_ordered_set_node *n, *prev = NULL;
for (n = l->first; n && (cmp = l->cmp(ptr, n->data)) > 0; n = n->next)

208
src/posix-compat.c Normal file
View file

@ -0,0 +1,208 @@
#include "posix-compat.h"
#include "process.h"
#ifdef VANILLA_WIN32
# define VC_EXTRALEAN
# define WIN32_LEAN_AND_MEAN
# 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);
#else
# include <unistd.h>
# include <sys/wait.h>
# include <sys/signal.h>
# include <sys/fcntl.h>
#endif
#include <csptr/smart_ptr.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
static struct criterion_test child_test;
static struct criterion_test_extra_data child_test_data;
static struct criterion_suite child_suite;
static struct criterion_test_extra_data child_suite_data;
static struct pipe_handle child_pipe;
#endif
int resume_child(void) {
if (g_worker_context.test) {
run_worker(&g_worker_context);
return 1;
}
return 0;
}
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
f_worker_func child_func = g_worker_context.func;
child_test = *g_worker_context.test;
child_test_data = *g_worker_context.test->data;
child_suite = *g_worker_context.suite;
child_pipe = *g_worker_context.pipe;
g_worker_context = (struct worker_context) {
&child_test,
&child_suite,
child_func,
&child_pipe
};
child_test.data = &child_test_data;
if (g_worker_context.suite->data) {
child_suite_data = *g_worker_context.suite->data;
child_suite.data = &child_suite_data;
WRITE_PROCESS_(info.hProcess, child_suite_data, sizeof (child_suite_data));
}
WRITE_PROCESS_(info.hProcess, child_test, sizeof (child_test));
WRITE_PROCESS_(info.hProcess, child_test_data, sizeof (child_test_data));
WRITE_PROCESS_(info.hProcess, child_suite, sizeof (child_suite));
WRITE_PROCESS_(info.hProcess, child_pipe, sizeof (child_pipe));
WRITE_PROCESS_(info.hProcess, g_worker_context, sizeof (struct worker_context));
ResumeThread(info.hThread);
CloseHandle(info.hThread);
return unique_ptr(s_proc_handle, { info.hProcess });
#else
pid_t pid = fork();
if (pid == -1)
return (void *) -1;
if (pid == 0)
return NULL;
return unique_ptr(s_proc_handle, { pid });
#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);
*status = exit_code << 8;
#else
waitpid(handle->pid, status, 0);
#endif
}
FILE *pipe_in(s_pipe_handle *p) {
#ifdef VANILLA_WIN32
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
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) {
#ifdef VANILLA_WIN32
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
close(p->fds[0]);
FILE *out = fdopen(p->fds[1], "w");
#endif
if (!out)
return NULL;
setvbuf(out, NULL, _IONBF, 0);
return out;
}
s_pipe_handle *stdpipe() {
#ifdef VANILLA_WIN32
HANDLE fhs[2];
SECURITY_ATTRIBUTES attr = {
.nLength = sizeof (SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE
};
if (!CreatePipe(fhs, fhs + 1, &attr, 0))
return NULL;
return unique_ptr(s_pipe_handle, {{ fhs[0], fhs[1] }});
#else
int fds[2] = { -1, -1 };
if (pipe(fds) == -1)
return NULL;
return unique_ptr(s_pipe_handle, {{ fds[0], fds[1] }});
#endif
}
s_proc_handle *get_current_process() {
#ifdef VANILLA_WIN32
return unique_ptr(s_proc_handle, { GetCurrentProcess() });
#else
return unique_ptr(s_proc_handle, { getpid() });
#endif
}
bool is_current_process(s_proc_handle *proc) {
#ifdef VANILLA_WIN32
return GetProcessId(proc->handle) == GetProcessId(GetCurrentProcess());
#else
return proc->pid == getpid();
#endif
}

56
src/posix-compat.h Normal file
View file

@ -0,0 +1,56 @@
#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)
# 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);
FILE *pipe_out(s_pipe_handle *p);
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 /* !POSIX_COMPAT_H_ */

View file

@ -22,77 +22,98 @@
* THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <csptr/smart_ptr.h>
#include "criterion/types.h"
#include "criterion/options.h"
#include "process.h"
#include "event.h"
#include "posix-compat.h"
struct process {
pid_t pid;
int in;
s_proc_handle *proc;
FILE *in;
};
static pid_t g_runner_pid;
static s_proc_handle *g_current_proc;
void set_runner_pid(void) {
g_runner_pid = getpid();
void set_runner_process(void) {
g_current_proc = get_current_process();
}
void unset_runner_process(void) {
sfree(g_current_proc);
}
bool is_runner(void) {
return g_runner_pid == getpid();
return is_current_process(g_current_proc);
}
static void close_process(void *ptr, UNUSED void *meta) {
close(((struct process *) ptr)->in);
struct process *proc = ptr;
fclose(proc->in);
sfree(proc->proc);
}
struct event *worker_read_event(struct process *proc) {
return read_event(proc->in);
}
void run_worker(struct worker_context *ctx) {
fclose(stdin);
g_event_pipe = pipe_out(ctx->pipe);
ctx->func(ctx->test, ctx->suite);
fclose(g_event_pipe);
fflush(NULL); // flush all opened streams
if (criterion_options.no_early_exit)
return;
_Exit(0);
}
struct process *spawn_test_worker(struct criterion_test *test,
struct criterion_suite *suite,
void (*func)(struct criterion_test *, struct criterion_suite *)) {
int fds[2];
if (pipe(fds) == -1)
f_worker_func func) {
smart s_pipe_handle *pipe = stdpipe();
if (pipe == NULL)
abort();
pid_t pid = fork();
if (pid == -1) {
g_worker_context = (struct worker_context) {
.test = test,
.suite = suite,
.func = func,
.pipe = pipe
};
s_proc_handle *proc = fork_process();
if (proc == (void *) -1) {
return NULL;
} else if (proc == NULL) {
run_worker(&g_worker_context);
return NULL;
} else if (!pid) {
close(STDIN_FILENO);
close(fds[0]);
EVENT_PIPE = fds[1];
func(test, suite);
close(fds[1]);
fflush(NULL); // flush all opened streams
if (criterion_options.no_early_exit)
return NULL;
else
_exit(0);
}
close(fds[1]);
return unique_ptr(struct process, { .pid = pid, .in = fds[0] }, close_process);
return unique_ptr(struct process,
.value = { .proc = proc, .in = pipe_in(pipe) },
.dtor = close_process);
}
struct process_status wait_proc(struct process *proc) {
int status;
waitpid(proc->pid, &status, 0);
wait_process(proc->proc, &status);
if (WIFEXITED(status))
return (struct process_status) { .kind = EXIT_STATUS, .status = WEXITSTATUS(status) };
return (struct process_status) {
.kind = EXIT_STATUS,
.status = WEXITSTATUS(status)
};
if (WIFSIGNALED(status))
return (struct process_status) { .kind = SIGNAL, .status = WTERMSIG(status) };
return (struct process_status) {
.kind = SIGNAL,
.status = WTERMSIG(status)
};
return (struct process_status) { .kind = STOPPED };
}

View file

@ -25,6 +25,7 @@
# define PROCESS_H_
# include <stdbool.h>
# include "posix-compat.h"
struct process;
@ -39,7 +40,9 @@ struct process_status {
int status;
};
void set_runner_pid(void);
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 *spawn_test_worker(struct criterion_test *test,

View file

@ -24,13 +24,17 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fnmatch.h>
#include "criterion/types.h"
#include "criterion/stats.h"
#include "criterion/logging.h"
#include "criterion/options.h"
#include "criterion/ordered-set.h"
#include "report.h"
#include "config.h"
#ifdef HAVE_FNMATCH
#include <fnmatch.h>
#endif
#define IMPL_CALL_REPORT_HOOKS(Kind) \
IMPL_SECTION_LIMITS(f_report_hook, crit_ ## Kind); \
@ -46,24 +50,29 @@
IMPL_CALL_REPORT_HOOKS(Type); \
ReportHook(Type)
#define log(Type, Arg) \
(criterion_options.output_provider->log_ ## Type ?: nothing)(Arg);
__attribute__((always_inline))
static inline void nothing() {}
IMPL_REPORT_HOOK(PRE_ALL)(struct criterion_test_set *set) {
if (criterion_options.pattern) {
FOREACH_SET(struct criterion_suite_set *s, set->suites) {
if ((s->suite.data && s->suite.data->disabled) || !s->tests)
continue;
#ifdef HAVE_FNMATCH
void disable_unmatching(struct criterion_test_set *set) {
FOREACH_SET(struct criterion_suite_set *s, set->suites) {
if ((s->suite.data && s->suite.data->disabled) || !s->tests)
continue;
FOREACH_SET(struct criterion_test *test, s->tests) {
if (fnmatch(criterion_options.pattern, test->data->identifier_, 0))
test->data->disabled = true;
}
FOREACH_SET(struct criterion_test *test, s->tests) {
if (fnmatch(criterion_options.pattern, test->data->identifier_, 0))
test->data->disabled = true;
}
}
}
#endif
IMPL_REPORT_HOOK(PRE_ALL)(struct criterion_test_set *set) {
#ifdef HAVE_FNMATCH
if (criterion_options.pattern) {
disable_unmatching(set);
}
#endif
log(pre_all, set);
}

View file

@ -43,4 +43,7 @@ DECL_CALL_REPORT_HOOKS(POST_FINI);
DECL_CALL_REPORT_HOOKS(POST_SUITE);
DECL_CALL_REPORT_HOOKS(POST_ALL);
#define log(Type, Arg) \
(criterion_options.output_provider->log_ ## Type ?: nothing)(Arg);
#endif /* !REPORT_H_ */

View file

@ -23,7 +23,6 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <csptr/smart_ptr.h>
#include "criterion/criterion.h"
#include "criterion/options.h"
@ -34,11 +33,15 @@
#include "event.h"
#include "process.h"
#include "timer.h"
#include "posix-compat.h"
#include "abort.h"
IMPL_SECTION_LIMITS(struct criterion_test, criterion_tests);
IMPL_SECTION_LIMITS(struct criterion_suite, crit_suites);
TestSuite(default);
// This is here to make the test suite & test sections non-empty
TestSuite();
Test(,) {};
int cmp_suite(void *a, void *b) {
struct criterion_suite *s1 = a, *s2 = b;
@ -70,7 +73,11 @@ struct criterion_test_set *criterion_init(void) {
insert_ordered_set(suites, &css, sizeof (css));
}
size_t nb_tests = 0;
FOREACH_TEST_SEC(test) {
if (!*test->category)
continue;
struct criterion_suite_set css = {
.suite = { .name = test->category },
};
@ -79,11 +86,9 @@ struct criterion_test_set *criterion_init(void) {
s->tests = new_ordered_set(cmp_test, NULL);
insert_ordered_set(s->tests, test, sizeof(*test));
++nb_tests;
}
const size_t nb_tests = SECTION_END(criterion_tests)
- SECTION_START(criterion_tests);
return unique_ptr(struct criterion_test_set, {
suites,
nb_tests,
@ -95,7 +100,10 @@ typedef void (*f_test_run)(struct criterion_global_stats *,
struct criterion_test *,
struct criterion_suite *);
static void map_tests(struct criterion_test_set *set, struct criterion_global_stats *stats, f_test_run fun) {
static void map_tests(struct criterion_test_set *set,
struct criterion_global_stats *stats,
f_test_run fun) {
FOREACH_SET(struct criterion_suite_set *s, set->suites) {
if (!s->tests)
continue;
@ -122,7 +130,9 @@ static void map_tests(struct criterion_test_set *set, struct criterion_global_st
__attribute__ ((always_inline))
static inline void nothing() {}
static void run_test_child(struct criterion_test *test, struct criterion_suite *suite) {
static void run_test_child(struct criterion_test *test,
struct criterion_suite *suite) {
send_event(PRE_INIT, NULL, 0);
if (suite->data)
(suite->data->init ?: nothing)();
@ -130,8 +140,11 @@ static void run_test_child(struct criterion_test *test, struct criterion_suite *
send_event(PRE_TEST, NULL, 0);
struct timespec_compat ts;
timer_start(&ts);
(test->test ?: nothing)();
if (setup_abort_test()) {
timer_start(&ts);
(test->test ?: nothing)();
}
double elapsed_time;
if (!timer_end(&elapsed_time, &ts))
elapsed_time = -1;
@ -144,7 +157,9 @@ static void run_test_child(struct criterion_test *test, struct criterion_suite *
}
__attribute__((always_inline))
static inline bool is_disabled(struct criterion_test *t, struct criterion_suite *s) {
static inline bool is_disabled(struct criterion_test *t,
struct criterion_suite *s) {
return t->data->disabled || (s->data && s->data->disabled);
}
@ -165,7 +180,10 @@ static void run_test(struct criterion_global_stats *stats,
smart struct criterion_test_stats *test_stats = test_stats_init(test);
if (is_disabled(test, suite)) {
stat_push_event(stats, suite_stats, test_stats, &(struct event) { .kind = PRE_TEST });
stat_push_event(stats,
suite_stats,
test_stats,
&(struct event) { .kind = PRE_INIT });
return;
}
@ -173,14 +191,20 @@ static void run_test(struct criterion_global_stats *stats,
if (proc == NULL && !is_runner())
return;
bool test_started = false;
bool normal_finish = false;
struct event *ev;
while ((ev = worker_read_event(proc)) != NULL) {
stat_push_event(stats, suite_stats, test_stats, ev);
switch (ev->kind) {
case PRE_INIT: report(PRE_INIT, test); break;
case PRE_TEST: report(PRE_TEST, test); break;
case PRE_TEST: report(PRE_TEST, test);
test_started = true;
break;
case ASSERT: report(ASSERT, ev->data); break;
case POST_TEST: report(POST_TEST, test_stats); break;
case POST_TEST: report(POST_TEST, test_stats);
normal_finish = true;
break;
case POST_FINI: report(POST_FINI, test_stats); break;
}
sfree(ev);
@ -188,6 +212,16 @@ static void run_test(struct criterion_global_stats *stats,
struct process_status status = wait_proc(proc);
if (status.kind == SIGNAL) {
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 });
}
return;
}
test_stats->signal = status.status;
if (test->data->signal == 0) {
push_event(TEST_CRASH);
@ -200,10 +234,12 @@ static void run_test(struct criterion_global_stats *stats,
}
static int criterion_run_all_tests_impl(void) {
if (resume_child()) // (windows only) resume from the fork
return -1;
smart struct criterion_test_set *set = criterion_init();
report(PRE_ALL, set);
set_runner_pid();
smart struct criterion_global_stats *stats = stats_init();
map_tests(set, stats, run_test);
@ -216,9 +252,12 @@ static int criterion_run_all_tests_impl(void) {
}
int criterion_run_all_tests(void) {
set_runner_process();
int res = criterion_run_all_tests_impl();
unset_runner_process();
if (res == -1) // if this is the test worker terminating
_exit(0);
exit(0);
return criterion_options.always_succeed || res;
}

View file

@ -29,7 +29,7 @@
static void nothing() {};
static void push_pre_suite();
static void push_pre_test();
static void push_pre_init();
static void push_assert();
static void push_post_test();
static void push_test_crash();
@ -81,8 +81,8 @@ void stat_push_event(s_glob_stats *stats,
static void (*const handles[])(s_glob_stats *, s_suite_stats *, s_test_stats *, void *) = {
nothing, // PRE_ALL
push_pre_suite, // PRE_SUITE
nothing, // PRE_INIT
push_pre_test, // PRE_TEST
push_pre_init, // PRE_INIT
nothing, // PRE_TEST
push_assert, // ASSERT
push_test_crash, // TEST_CRASH
push_post_test, // POST_TEST
@ -106,7 +106,14 @@ static void push_pre_suite(s_glob_stats *stats,
++stats->nb_suites;
}
static void push_pre_test(s_glob_stats *stats,
__attribute__((always_inline))
static inline bool is_disabled(struct criterion_test *t,
struct criterion_suite *s) {
return t->data->disabled || (s->data && s->data->disabled);
}
static void push_pre_init(s_glob_stats *stats,
s_suite_stats *suite,
s_test_stats *test,
UNUSED void *ptr) {
@ -114,6 +121,11 @@ static void push_pre_test(s_glob_stats *stats,
suite->tests = sref(test);
++stats->nb_tests;
++suite->nb_tests;
if (is_disabled(test->test, suite->suite)) {
++stats->tests_skipped;
++suite->tests_skipped;
}
}
static void destroy_assert(void *ptr, UNUSED void *meta) {