diff --git a/.ci/install-pip.sh b/.ci/install-pip.sh new file mode 100755 index 0000000..379cfbf --- /dev/null +++ b/.ci/install-pip.sh @@ -0,0 +1,4 @@ +#!/bin/sh +curl -O https://bootstrap.pypa.io/get-pip.py +python3 get-pip.py +rm -f get-pip.py diff --git a/.gitignore b/.gitignore index a807c71..c1f4e54 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ !*.h !*.rst !samples/tests/*.sh +!*.po !LICENSE !HEADER diff --git a/.travis.yml b/.travis.yml index 95ce90a..1624538 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/Makefile.am b/Makefile.am index e880bad..87b4321 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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) diff --git a/README.md b/README.md index a0537f7..5974341 100644 --- a/README.md +++ b/README.md @@ -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/ diff --git a/appveyor.yml b/appveyor.yml index 5ef46fa..76e605f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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`_, -and describe the problem you are experiencing. +and describe the problem you are experiencing, along with the platform you are +running criterion on. diff --git a/doc/hooks.rst b/doc/hooks.rst index 0596a9e..a9b8bd6 100644 --- a/doc/hooks.rst +++ b/doc/hooks.rst @@ -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 + #include + + ReportHook(PRE_TEST)() { + // not using the parameter + } + + ReportHook(PRE_TEST)(struct criterion_test *test) { + // using the parameter + } diff --git a/doc/index.rst b/doc/index.rst index 21a4a6c..dd79dd7 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -9,4 +9,5 @@ Criterion starter hooks env + internal faq diff --git a/doc/internal.rst b/doc/internal.rst new file mode 100644 index 0000000..8c94237 --- /dev/null +++ b/doc/internal.rst @@ -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/``. diff --git a/doc/starter.rst b/doc/starter.rst index b974bdd..7021a38 100644 --- a/doc/starter.rst +++ b/doc/starter.rst @@ -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 + + 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. diff --git a/include/criterion/abort.h b/include/criterion/abort.h new file mode 100644 index 0000000..f20e300 --- /dev/null +++ b/include/criterion/abort.h @@ -0,0 +1,31 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2015 Franklin "Snaipe" Mathieu + * + * 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_ */ diff --git a/include/criterion/assert.h b/include/criterion/assert.h index f17de27..4a5b2b4 100644 --- a/include/criterion/assert.h +++ b/include/criterion/assert.h @@ -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_ */ diff --git a/include/criterion/common.h b/include/criterion/common.h index 342e6db..e5ebfd2 100644 --- a/include/criterion/common.h +++ b/include/criterion/common.h @@ -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" diff --git a/include/criterion/event.h b/include/criterion/event.h index 75f4a7c..99c85c8 100644 --- a/include/criterion/event.h +++ b/include/criterion/event.h @@ -25,8 +25,9 @@ # define CRITERION_EVENT_H_ # include +# include -extern int EVENT_PIPE; +extern FILE *g_event_pipe; void send_event(int kind, void *data, size_t size); diff --git a/include/criterion/logging.h b/include/criterion/logging.h index 776361a..616dac9 100644 --- a/include/criterion/logging.h +++ b/include/criterion/logging.h @@ -25,6 +25,7 @@ # define CRITERION_LOGGING_H_ # include +# include # 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; diff --git a/include/criterion/ordered-set.h b/include/criterion/ordered-set.h index a8d1b11..4b71081 100644 --- a/include/criterion/ordered-set.h +++ b/include/criterion/ordered-set.h @@ -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_ */ diff --git a/include/criterion/stats.h b/include/criterion/stats.h index 6d84e8c..8a1b4e7 100644 --- a/include/criterion/stats.h +++ b/include/criterion/stats.h @@ -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; diff --git a/include/criterion/types.h b/include/criterion/types.h index 3a4672f..6e7dae2 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -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_ */ diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..527e861 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1 @@ +fr diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..70daf30 --- /dev/null +++ b/po/Makevars @@ -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 diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..d9d2b16 --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,2 @@ +# List of source files which contain translatable strings. +src/log/normal.c diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 0000000..ffbd217 --- /dev/null +++ b/po/fr.po @@ -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. +# , 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: \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" diff --git a/samples/Makefile.am b/samples/Makefile.am index 78d071a..7ed5f12 100644 --- a/samples/Makefile.am +++ b/samples/Makefile.am @@ -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 \ diff --git a/samples/asserts.c b/samples/asserts.c index 811824f..abd20b0 100644 --- a/samples/asserts.c +++ b/samples/asserts.c @@ -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); +} diff --git a/samples/description.c b/samples/description.c index 3e9f02a..335036b 100644 --- a/samples/description.c +++ b/samples/description.c @@ -3,3 +3,6 @@ Test(misc, failing, .description = "Just a failing test") { assert(0); } + +Test(misc, skipped, .description = "This one is skipped", .disabled = true) { +} diff --git a/samples/more-suites.c b/samples/more-suites.c index 701866a..1b2f14e 100644 --- a/samples/more-suites.c +++ b/samples/more-suites.c @@ -1,10 +1,12 @@ #include 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); diff --git a/samples/other-crashes.c b/samples/other-crashes.c new file mode 100644 index 0000000..a1baa27 --- /dev/null +++ b/samples/other-crashes.c @@ -0,0 +1,14 @@ +#include + +void crash(void) { + int *i = NULL; + *i = 42; +} + +Test(misc, setup_crash, .init = crash) { + assert(true); +} + +Test(misc, teardown_crash, .fini = crash) { + assert(true); +} diff --git a/samples/with-time.c b/samples/with-time.c new file mode 100644 index 0000000..1d67a01 --- /dev/null +++ b/samples/with-time.c @@ -0,0 +1,5 @@ +#include + +Test(samples, timed) { + assert(0); +} diff --git a/src/abort.c b/src/abort.c new file mode 100644 index 0000000..ea61bb9 --- /dev/null +++ b/src/abort.c @@ -0,0 +1,35 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2015 Franklin "Snaipe" Mathieu + * + * 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 +#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); +} diff --git a/src/abort.h b/src/abort.h new file mode 100644 index 0000000..4d85ba3 --- /dev/null +++ b/src/abort.h @@ -0,0 +1,31 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2015 Franklin "Snaipe" Mathieu + * + * 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 + +int setup_abort_test(void); + +#endif /* !ABORT_H_ */ diff --git a/src/event.c b/src/event.c index 339c477..cca6ce6 100644 --- a/src/event.c +++ b/src/event.c @@ -22,39 +22,44 @@ * THE SOFTWARE. */ -#include +#include #include #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(); } diff --git a/src/event.h b/src/event.h index 405df73..fc8ce4f 100644 --- a/src/event.h +++ b/src/event.h @@ -31,6 +31,6 @@ struct event { void *data; }; -struct event *read_event(int fd); +struct event *read_event(FILE *f); #endif /* !EVENT_H_ */ diff --git a/src/i18n.c b/src/i18n.c new file mode 100644 index 0000000..5b9899b --- /dev/null +++ b/src/i18n.c @@ -0,0 +1,8 @@ +#include "i18n.h" + +#if ENABLE_NLS +__attribute__ ((constructor)) +void init_i18n(void) { + bindtextdomain (PACKAGE, LOCALEDIR); +} +#endif diff --git a/src/i18n.h b/src/i18n.h new file mode 100644 index 0000000..74c159e --- /dev/null +++ b/src/i18n.h @@ -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 +# define _(String) dgettext(PACKAGE, String) +# define _s(String, Plural, Quantity) \ + dngettext(PACKAGE, String, Plural, (Quantity)) +# endif + +#endif /* !I18N_H_ */ diff --git a/src/log/logging.c b/src/log/logging.c index f26d1e2..5d2bc8d 100644 --- a/src/log/logging.c +++ b/src/log/logging.c @@ -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 #include +#include #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); +} diff --git a/src/log/normal.c b/src/log/normal.c index 3e65fb4..42645be 100644 --- a/src/log/normal.c +++ b/src/log/normal.c @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #define _GNU_SOURCE +#define CRITERION_LOGGING_COLORS #include #include #include @@ -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, }; diff --git a/src/main.c b/src/main.c index 467e49d..32859c9 100644 --- a/src/main.c +++ b/src/main.c @@ -26,13 +26,26 @@ #include #include #include +#include #include #include #include "runner.h" #include "config.h" +#if ENABLE_NLS +# include +#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; diff --git a/src/options.c b/src/options.c index 9b47c44..c83b294 100644 --- a/src/options.c +++ b/src/options.c @@ -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, +}; diff --git a/src/ordered-set.c b/src/ordered-set.c index 220b56a..9386b59 100644 --- a/src/ordered-set.c +++ b/src/ordered-set.c @@ -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) diff --git a/src/posix-compat.c b/src/posix-compat.c new file mode 100644 index 0000000..e68f848 --- /dev/null +++ b/src/posix-compat.c @@ -0,0 +1,208 @@ +#include "posix-compat.h" +#include "process.h" + +#ifdef VANILLA_WIN32 +# define VC_EXTRALEAN +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +# include +# include + +# 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 +# include +# include +# include +#endif + +#include + +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 +} diff --git a/src/posix-compat.h b/src/posix-compat.h new file mode 100644 index 0000000..477361e --- /dev/null +++ b/src/posix-compat.h @@ -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 +# 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 +# endif + +#include + +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_ */ diff --git a/src/process.c b/src/process.c index 0d5689c..d3a5f9e 100644 --- a/src/process.c +++ b/src/process.c @@ -22,77 +22,98 @@ * THE SOFTWARE. */ #include -#include -#include -#include +#include #include #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 }; } diff --git a/src/process.h b/src/process.h index 02f480e..5e0a6c3 100644 --- a/src/process.h +++ b/src/process.h @@ -25,6 +25,7 @@ # define PROCESS_H_ # include +# 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, diff --git a/src/report.c b/src/report.c index 5c70807..ea13806 100644 --- a/src/report.c +++ b/src/report.c @@ -24,13 +24,17 @@ #define _GNU_SOURCE #include #include -#include #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 +#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); } diff --git a/src/report.h b/src/report.h index 29e3a97..6a740c5 100644 --- a/src/report.h +++ b/src/report.h @@ -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_ */ diff --git a/src/runner.c b/src/runner.c index c4a535e..b19ea48 100644 --- a/src/runner.c +++ b/src/runner.c @@ -23,7 +23,6 @@ */ #include #include -#include #include #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; } diff --git a/src/stats.c b/src/stats.c index a817ec3..3d27db2 100644 --- a/src/stats.c +++ b/src/stats.c @@ -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) {