diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a3c3c426..77c74087 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,6 +1,6 @@ [bumpversion] current_version = 1.3.1 -commit = True +commit = False [bumpversion:file:CMakeLists.txt] diff --git a/.ci/install-libcsptr.sh b/.ci/install-libcsptr.sh deleted file mode 100755 index 088f06bb..00000000 --- a/.ci/install-libcsptr.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -repo="https://github.com/Snaipe/libcsptr.git" -tag="v2.0.4" - -mkdir dependencies -git clone --branch ${tag} --depth 1 ${repo} dependencies/libcsptr && -( - cd dependencies/libcsptr && - mkdir build && - cd $_ && - cmake -DCMAKE_INSTALL_PREFIX=$LOCAL_INSTALL "$@" .. && - make && - make install -) diff --git a/.ci/install-pip.sh b/.ci/install-pip.sh deleted file mode 100755 index 379cfbf1..00000000 --- a/.ci/install-pip.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -curl -O https://bootstrap.pypa.io/get-pip.py -python3 get-pip.py -rm -f get-pip.py diff --git a/.cmake/Modules/FindDyncall.cmake b/.cmake/Modules/FindDyncall.cmake new file mode 100644 index 00000000..83070f6f --- /dev/null +++ b/.cmake/Modules/FindDyncall.cmake @@ -0,0 +1,31 @@ +# Copyright (C) 2015 Franklin "Snaipe" Mathieu. +# Redistribution and use of this file is allowed according to the terms of the MIT license. +# For details see the LICENSE file distributed with Criterion. + +# - Find dyncall +# Find the native libcsptr headers and libraries. +# +# DYNCALL_INCLUDE_DIRS - where to find smart_ptr.h, etc. +# DYNCALL_LIBRARIES - List of libraries when using libcsptr. +# DYNCALL_FOUND - True if libcsptr has been found. + +# Look for the header file. +FIND_PATH(DYNCALL_INCLUDE_DIR dyncall.h) + +# Look for the library. +FIND_LIBRARY(DYNCALL_LIBRARY NAMES dyncall_s) + +# Handle the QUIETLY and REQUIRED arguments and set DYNCALL_FOUND to TRUE if all listed variables are TRUE. +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DYNCALL DEFAULT_MSG DYNCALL_LIBRARY DYNCALL_INCLUDE_DIR) + +# Copy the results to the output variables. +IF(DYNCALL_FOUND) + SET(DYNCALL_LIBRARIES ${DYNCALL_LIBRARY}) + SET(DYNCALL_INCLUDE_DIRS ${DYNCALL_INCLUDE_DIR}) +ELSE(DYNCALL_FOUND) + SET(DYNCALL_LIBRARIES) + SET(DYNCALL_INCLUDE_DIRS) +ENDIF(DYNCALL_FOUND) + +MARK_AS_ADVANCED(DYNCALL_INCLUDE_DIRS DYNCALL_LIBRARIES) diff --git a/.cmake/Modules/Submodules.cmake b/.cmake/Modules/Submodules.cmake new file mode 100644 index 00000000..5183368e --- /dev/null +++ b/.cmake/Modules/Submodules.cmake @@ -0,0 +1,56 @@ +if(EXISTS "${PROJECT_SOURCE_DIR}/.gitmodules") +message(STATUS "Updating submodules to their latest/fixed versions") +message(STATUS "(this can take a while, please be patient)") + +### set the direcory where the submodules live +set(GIT_SUBMODULES_DIRECTORY dependencies) + +### set the directory names of the submodules +set(GIT_SUBMODULES libcsptr dyncall) + +### set each submodules's commit or tag that is to be checked out +### (leave empty if you want master) + +### First, get all submodules in +if(${GIT_SUBMODULES_CHECKOUT_QUIET}) + execute_process( + COMMAND git submodule update --init --recursive + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_QUIET + ERROR_QUIET + ) +else() + execute_process( + COMMAND git submodule update --init --recursive + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + ) +endif() + +### Then, checkout each submodule to the specified commit +# Note: Execute separate processes here, to make sure each one is run, +# should one crash (because of branch not existing, this, that ... whatever) +foreach(GIT_SUBMODULE ${GIT_SUBMODULES}) + + if( "${GIT_SUBMODULE_VERSION_${GIT_SUBMODULE}}" STREQUAL "" ) + message(STATUS "no specific version given for submodule ${GIT_SUBMODULE}, checking out master") + set(GIT_SUBMODULE_VERSION_${GIT_SUBMODULE} "master") + endif() + + if(${GIT_SUBMODULES_CHECKOUT_QUIET}) + execute_process( + COMMAND git checkout ${GIT_SUBMODULE_VERSION_${GIT_SUBMODULE}} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/${GIT_SUBMODULES_DIRECTORY}/${GIT_SUBMODULE} + OUTPUT_QUIET + ERROR_QUIET + ) + else() + message(STATUS "checking out ${GIT_SUBMODULE}'s commit/tag ${GIT_SUBMODULE_VERSION_${GIT_SUBMODULE}}") + execute_process( + COMMAND git checkout -q ${GIT_SUBMODULE_VERSION_${GIT_SUBMODULE}} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/${GIT_SUBMODULES_DIRECTORY}/${GIT_SUBMODULE} + ) + endif() + +endforeach(${GIT_SUBMODULE}) + +endif() diff --git a/.gitignore b/.gitignore index 8dd4a197..e28ebbfa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,24 +3,31 @@ !.gitignore !.bumpversion.cfg +!.ci/* +!.cmake/* + +!dev/* +!doc/* !*.c +!*.cc !*.h !*.rst -!samples/tests/*.sh !*.po +!*.in +!samples/tests/*.sh !samples/*.expected !LICENSE !HEADER !ChangeLog +CMakeFiles/ !CMakeLists.txt -!.cmake/* -!src/config.h.in src/config.h - build + *~ *.swp +.*.swp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..7c24be15 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "dependencies/libcsptr"] + path = dependencies/libcsptr + url = https://github.com/Snaipe/libcsptr.git +[submodule "dependencies/dyncall"] + path = dependencies/dyncall + url = https://github.com/Snaipe/dyncall.git +[submodule "dependencies/wingetopt"] + path = dependencies/wingetopt + url = https://github.com/alex85k/wingetopt.git diff --git a/.travis.yml b/.travis.yml index 4df2cb7f..dd3282b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,19 +2,32 @@ language: c os: - linux - osx + compiler: -- gcc +- gcc-4.9 + sudo: false + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.9 + - g++-4.9 + before_install: +- export GCOV="gcov-4.9" - export LOCAL_INSTALL="$HOME" -- ".ci/install-libcsptr.sh" - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/lib - export CFLAGS="-g -O0" +- export CXX="g++-4.9" script: - mkdir -p build - cd build - cmake -DCOVERALLS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=$HOME -DCMAKE_INSTALL_PREFIX=criterion-${TRAVIS_TAG} .. - make +- make criterion_tests - make test after_success: - make coveralls diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b8ad202..ef4b09e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,22 +1,49 @@ cmake_minimum_required(VERSION 2.8) -project(Criterion C) +project(Criterion C CXX) -# Project setup & environment variables +set(MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.cmake/Modules") +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${MODULE_DIR}) +set(LIBCSPTR_DISABLE_TESTS ON) +set(LIBCSPTR_DISABLE_COVERALLS ON) -enable_testing() -add_subdirectory(samples) +include(Submodules) + +if (MSVC) + add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) +endif () + +add_subdirectory(dependencies/libcsptr/ EXCLUDE_FROM_ALL) +add_subdirectory(dependencies/dyncall/ EXCLUDE_FROM_ALL) + +include_directories( + dependencies/libcsptr/include/ + dependencies/dyncall/dyncall/ +) + +if (MSVC) + add_subdirectory(dependencies/wingetopt/ EXCLUDE_FROM_ALL) + include_directories(dependencies/wingetopt/src/) +endif () + +# Project setup & environment variables set(PROJECT_VERSION "1.3.1") set(LOCALEDIR ${CMAKE_INSTALL_PREFIX}/share/locale) set(GettextTranslate_ALL 1) set(GettextTranslate_GMO_BINARY 1) -set(MODULE_DIR "${CMAKE_SOURCE_DIR}/.cmake/Modules") -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${MODULE_DIR}) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -std=gnu99") +add_definitions(-DCRITERION_BUILDING_DLL=1) + +if (NOT MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -std=gnu99") +endif () + +if (MSVC) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO") +endif () -if (WIN32) +if (WIN32 AND NOT MSVC) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-no-undefined") endif() @@ -44,7 +71,6 @@ include(CheckLibraryExists) CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME) find_package(PCRE) -find_package(Libcsptr REQUIRED) # List sources and headers @@ -71,7 +97,11 @@ set(SOURCE_FILES src/i18n.h src/ordered-set.c src/posix-compat.c + src/theories.c + src/asprintf.c + src/file.c src/main.c + src/entry.c ) if (PCRE_FOUND) @@ -94,18 +124,26 @@ set(INTERFACE_FILES include/criterion/options.h include/criterion/ordered-set.h include/criterion/stats.h + include/criterion/theories.h + include/criterion/asprintf-compat.h + include/criterion/designated-initializer-compat.h + include/criterion/preprocess.h ) # Generate the configure file configure_file( - "${CMAKE_SOURCE_DIR}/src/config.h.in" - "${CMAKE_SOURCE_DIR}/src/config.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" + "${CMAKE_CURRENT_SOURCE_DIR}/src/config.h" ) -include_directories(include src ${CSPTR_INCLUDE_DIRS}) +include_directories(include src) add_library(criterion SHARED ${SOURCE_FILES} ${INTERFACE_FILES}) -target_link_libraries(criterion ${CSPTR_LIBRARIES}) +target_link_libraries(criterion csptr dyncall_s) + +if (MSVC) + target_link_libraries(criterion wingetopt) +endif () if (HAVE_CLOCK_GETTIME) target_link_libraries(criterion rt) @@ -130,6 +168,8 @@ install(TARGETS criterion ARCHIVE DESTINATION lib ) -add_custom_target(uninstall - "${CMAKE_COMMAND}" -P "${CMAKE_MODULE_PATH}/uninstall.cmake" -) +add_custom_target(criterion_tests) + +enable_testing() +add_subdirectory(samples) +add_subdirectory(test) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..670555eb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# 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 pass all tests. +* 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?") + +## Roadmap + + . + |- .cmake/: CMake modules + |- dependencies/: dependencies for building libcriterion + |- doc/: Sphinx documentation files + |- include/criterion/: Public API + |- src/: Sources for libcriterion + | `- log/: Output providers, all the output logic in general + |- po/: Translation files, i18n stuff + |- test/: Unit tests for libcriterion + `- samples/: Sample files + |- outputs/: Expected output files for the current samples + `- tests/: Internal regression tests + `- outputs/: Expected output files for the regression tests diff --git a/ChangeLog b/ChangeLog index 514345a7..dc3eead5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2015-09-14 Franklin "Snaipe" Mathieu + + * criterion: version 2.0.0 + * Breaking: ABI incompatibility with prior versions of criterion. You + **must** recompile your tests. + * Breaking: cr_abort_test(NULL) does not compile anymore. + * Change: Changed all assertion macros to accept a printf format string as a + message. + * Change: Made the API C++11-compatible. + * Change: Made the library ISO C compliant. You can now compile the library + with VC 14+. + * Addition: Added support for theories. + * Addition: Added ability to test the exit status of a test. + * Addition: Added C++11 throw assertions. + * Addition: Added assert message localization. + * Addition: Added test timeouts. + * Addition: Added test standard i/o redirection & file comparison assertions. + * Removal: Removed the deprecated prefixless assertion macros + * Deprecation: Deprecated cr_abort_test. + * Deprecation: cr_{assert,expect}_strings_* and cr_{assert,expect}_arrays_* + are deprecated in favor of cr_{assert,expect}_str_* and + cr_{assert,expect}_arr_* respectively. + 2015-08-20 Franklin "Snaipe" Mathieu * criterion: version 1.3.1 diff --git a/README.md b/README.md index b68c6c16..a1b09629 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,13 @@ Criterion Logo ========= -[![Build Status](https://travis-ci.org/Snaipe/Criterion.svg?branch=bleeding)](https://travis-ci.org/Snaipe/Criterion) +[![Unix Build Status](https://travis-ci.org/Snaipe/Criterion.svg?branch=bleeding)](https://travis-ci.org/Snaipe/Criterion) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/Snaipe/Criterion?svg=true&branch=bleeding)](https://ci.appveyor.com/project/Snaipe/Criterion/branch/bleeding) [![Coverage Status](https://coveralls.io/repos/Snaipe/Criterion/badge.svg?branch=bleeding)](https://coveralls.io/r/Snaipe/Criterion?branch=bleeding) [![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) -A dead-simple, yet extensible, C unit testing framework. +A dead-simple, yet extensible, C and C++ unit testing framework. ![Screencast](./doc/screencast.gif) @@ -23,30 +24,35 @@ This gives the user great control, at the unfortunate cost of simplicity. Criterion follows the KISS principle, while keeping the control the user would have with other frameworks: +* [x] C99 and C++11 compatible. * [x] Tests are automatically registered when declared. * [x] Implements a xUnit framework structure. * [x] A default entry point is provided, no need to declare a main unless you want to do special handling. * [x] Test are isolated in their own process, crashes and signals can be reported and tested. +* [x] Unified interface between C and C++: include the criterion header and it *just* works. +* [x] There is a support for theories alongside tests * [x] Progress and statistics can be followed in real time with report hooks. * [x] TAP output format can be enabled with an option. -* [x] Runs on Linux, FreeBSD, Mac OS X, and Windows (Compiling with MinGW GCC). +* [x] Runs on Linux, FreeBSD, Mac OS X, and Windows (Compiling with MinGW GCC and Visual Studio 2015+). ## Downloads -* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.1/criterion-1.3.1-linux-x86_64.tar.bz2) -* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.1/criterion-1.3.1-osx-x86_64.tar.bz2) -* [Windows (x86)](https://github.com/Snaipe/Criterion/releases/download/v1.3.1/criterion-1.3.1-windows-x86.tar.bz2) +* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.1/criterion-v1.3.1-linux-x86_64.tar.bz2) +* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.1/criterion-v1.3.1-osx-x86_64.tar.bz2) +* [Windows (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.1/criterion-v1.3.1-windows-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) -## Documentation +## Developer Resources + +### Documentation An online documentation is available on [ReadTheDocs][online-docs] ([PDF][pdf-docs] | [Zip][zip-docs] | [Epub][epub-docs]) -## Samples +### Samples Sample tests can be found in the [sample directory][samples]. @@ -57,37 +63,17 @@ 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.** +### Getting help -### General +Gitter.im chat room: [![Join the chat at https://gitter.im/Snaipe/Criterion](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Snaipe/Criterion?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -* 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. +### Misc -### 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?") +* [CMake find module for Criterion][find-module] ## F.A.Q. -**Q. What's wrong with other test frameworks?** +**Q. What's wrong with other C test frameworks?** A. I worked with CUnit and Check, and I must say that they do their job very well -- the only thing that bugs me is that setting up a test suite from scratch is a pain, it should really be simpler. Most @@ -116,3 +102,5 @@ Logo done by [Greehm](http://www.cargocollective.com/pbouigue) [sample-fixtures]: ./samples/fixtures.c [sample-signal]: ./samples/signal.c [sample-report]: ./samples/report.c + +[find-module]: ./dev/FindCriterion.cmake diff --git a/appveyor.yml b/appveyor.yml index 1f74e15f..66c09d8a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,10 @@ version: 1.3.1_b{build}-{branch} -os: Windows Server 2012 +os: Visual Studio 2015 init: - git config --global core.autocrlf input - - 'SET PATH=%PATH%;C:\MinGW\msys\1.0\bin;C:\MinGW\bin;%APPVEYOR_BUILD_FOLDER%\bin;%APPVEYOR_BUILD_FOLDER%\build' + - 'SET PATH=C:\MinGW\bin;%PATH%;C:\MinGW\msys\1.0\bin;%APPVEYOR_BUILD_FOLDER%\build;%APPVEYOR_BUILD_FOLDER%\build\Debug' environment: COVERALLS_REPO_TOKEN: @@ -13,40 +13,50 @@ environment: CI_JOB_ID: $(APPVEYOR_JOB_ID) LOCAL_INSTALL: $(APPVEYOR_BUILD_FOLDER) GCOV_PREFIX: $(APPVEYOR_BUILD_FOLDER) + matrix: + - COMPILER: mingw + GENERATOR: "MSYS Makefiles" + - COMPILER: msvc + GENERATOR: "Visual Studio 14 2015" clone_depth: 5 -matrix: - fast_finish: true # set this flag to immediately finish build once one of the jobs fails. - platform: - x86_64 configuration: Release install: + - ps: $env:RELEASE_NAME = $env:APPVEYOR_REPO_BRANCH -replace "/", "-" # Hack to make git think it is on the tip of the repo branch - 'git checkout -B %APPVEYOR_REPO_BRANCH%' - # Install libcsptr - - 'bash -lc "cd $APPVEYOR_BUILD_FOLDER; .ci/install-libcsptr.sh -G \"MSYS Makefiles\"; true"' - # We need to manually make since the above command somehow crashes - - 'cd dependencies/libcsptr/build && make && make install && cd ../../../' # Configure project - 'mkdir build && cd build' - - 'cmake -DCMAKE_INSTALL_PREFIX=criterion-%APPVEYOR_REPO_TAG_NAME% -DCMAKE_PREFIX_PATH="%LOCAL_INSTALL%" -DCOVERALLS=ON -DCMAKE_BUILD_TYPE=Debug -G "MSYS Makefiles" ..' + - > + cmake + -Wno-dev + -DCMAKE_INSTALL_PREFIX="criterion-%RELEASE_NAME%" + -DCMAKE_PREFIX_PATH="%LOCAL_INSTALL%" + -G "%GENERATOR%" + .. build_script: - - 'make' + - cmake --build . -after_build: - - 'make install' - - 'bash -lc "cd $APPVEYOR_BUILD_FOLDER; tar -cvjf criterion-${APPVEYOR_REPO_BRANCH}-windows-${PLATFORM}.tar.bz2 -C build criterion-${APPVEYOR_REPO_TAG_NAME}"' +before_deploy: + - ps: | + $archive = "criterion-$env:RELEASE_NAME-windows-$env:COMPILER-$env:PLATFORM" + cmake --build . --target install + 7z a -ttar "$archive.tar" "criterion-$env:RELEASE_NAME" + 7z a -tbzip2 "../$archive.tar.bz2" "$archive.tar" + Push-AppveyorArtifact "../$archive.tar.bz2" test_script: - - 'make test || bash -lc "cat $APPVEYOR_BUILD_FOLDER/build/Testing/Temporary/LastTest.log"' + - cmake --build . --target criterion_tests + - ps: try { ctest } catch { type Testing/Temporary/LastTest.log } -after_test: - - 'make coveralls' +#after_test: +# - 'make coveralls' notifications: @@ -54,14 +64,11 @@ notifications: to: [franklinmathieu@gmail.com] on_build_status_changed: true -artifacts: - - path: '**\*.tar.bz2' - deploy: provider: GitHub auth_token: secure: MnZZQeoxBVnpV9GSSvVok5Je0/N2d/fzG4+ITw95/tYSgZ8rleBV23a5sCwAea3r - artifact: 'criterion-$(APPVEYOR_REPO_TAG_NAME)-windows-$(PLATFORM).tar.bz2' + artifact: 'criterion-$(RELEASE_NAME)-windows-$(COMPILER)-$(PLATFORM).tar.bz2' draft: false prerelease: false on: diff --git a/dependencies/dyncall b/dependencies/dyncall new file mode 160000 index 00000000..68c57f66 --- /dev/null +++ b/dependencies/dyncall @@ -0,0 +1 @@ +Subproject commit 68c57f664d4fabbc5b80327fbf5661a3a5a51e06 diff --git a/dependencies/libcsptr b/dependencies/libcsptr new file mode 160000 index 00000000..2762164a --- /dev/null +++ b/dependencies/libcsptr @@ -0,0 +1 @@ +Subproject commit 2762164acfaa712fea7dec6ed760ff88f7d2e026 diff --git a/dependencies/wingetopt b/dependencies/wingetopt new file mode 160000 index 00000000..76a5d1ab --- /dev/null +++ b/dependencies/wingetopt @@ -0,0 +1 @@ +Subproject commit 76a5d1ab15f684d4c9479a32624d65b3bd1a726b diff --git a/dev/FindCriterion.cmake b/dev/FindCriterion.cmake new file mode 100644 index 00000000..1ca96d90 --- /dev/null +++ b/dev/FindCriterion.cmake @@ -0,0 +1,27 @@ +# This file is licensed under the WTFPL version 2 -- you can see the full +# license over at http://www.wtfpl.net/txt/copying/ +# +# - Try to find Criterion +# +# Once done this will define +# CRITERION_FOUND - System has LibXml2 +# CRITERION_INCLUDE_DIRS - The LibXml2 include directories +# CRITERION_LIBRARIES - The libraries needed to use LibXml2 + +find_package(PkgConfig) + +find_path(CRITERION_INCLUDE_DIR criterion/criterion.h + PATH_SUFFIXES criterion) + +find_library(CRITERION_LIBRARY NAMES criterion libcriterion) + +set(CRITERION_LIBRARIES ${CRITERION_LIBRARY}) +set(CRITERION_INCLUDE_DIRS ${CRITERION_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set CRITERION_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(LibXml2 DEFAULT_MSG + CRITERION_LIBRARY CRITERION_INCLUDE_DIR) + +mark_as_advanced(CRITERION_INCLUDE_DIR CRITERION_LIBRARY) diff --git a/doc/assert.rst b/doc/assert.rst new file mode 100644 index 00000000..46ad0044 --- /dev/null +++ b/doc/assert.rst @@ -0,0 +1,144 @@ +.. _assertions-ref: + +Assertion reference +=================== + +This is an exhaustive list of all assertion macros that Criterion provides. + +As each ``assert`` macros have an ``expect`` counterpart with the exact same +number of parameters and name suffix, there is no benefit in adding ``expect`` +macros to this list. Hence only ``assert`` macros are represented here. + +Common Assertions +----------------- + +======================================================================= =========================================================================== =========================================== +Macro Passes if and only if Notes +======================================================================= =========================================================================== =========================================== +cr_assert(Condition, [Message, [Args...]]) ``Condition`` is true. +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_not(Condition, [Message, [Args...]]) ``Condition`` is false. +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_null(Value, [Message, [Args...]]) ``Value`` is ``NULL``. +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_not_null(Value, [Message, [Args...]]) ``Value`` is not ``NULL``. +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_eq(Actual, Expected, [Message, [Args...]]) ``Actual`` is equal to ``Expected``. Compatible with C++ operator overloading +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_neq(Actual, Unexpected, [Message, [Args...]]) ``Actual`` is not equal to ``Unexpected``. Compatible with C++ operator overloading +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_lt(Actual, Reference, [Message, [Args...]]) ``Actual`` is less than ``Reference``. Compatible with C++ operator overloading +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_leq(Actual, Reference, [Message, [Args...]]) ``Actual`` is less or equal to ``Reference``. Compatible with C++ operator overloading +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_gt(Actual, Reference, [Message, [Args...]]) ``Actual`` is greater than ``Reference``. Compatible with C++ operator overloading +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_geq(Actual, Reference, [Message, [Args...]]) ``Actual`` is greater or equal to ``Reference``. Compatible with C++ operator overloading +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_float_eq(Actual, Expected, Epsilon, [Message, [Args...]]) ``Actual`` is equal to ``Expected`` with a tolerance of ``Epsilon``. Use this to test equality between floats +----------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_float_neq(Actual, Unexpected, Epsilon, [Message, [Args...]]) ``Actual`` is not equal to ``Unexpected`` with a tolerance of ``Epsilon``. Use this to test inequality between floats +======================================================================= =========================================================================== =========================================== + +String Assertions +----------------- + +Note: these macros are meant to deal with *native* strings, i.e. char arrays. +Most of them won't work on ``std::string`` in C++, with some exceptions -- for +``std::string``, you should use regular comparison assersions, as listed above. + +=========================================================== =================================================================== =========================================== +Macro Passes if and only if Notes +=========================================================== =================================================================== =========================================== +cr_assert_str_empty(Value, [Message, [Args...]]) ``Value`` is an empty string. Also works on std::string +----------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------- +cr_assert_str_not_empty(Value, [Message, [Args...]]) ``Value`` is not an empty string. Also works on std::string +----------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------- +cr_assert_str_eq(Actual, Expected, [Message, [Args...]]) ``Actual`` is lexicographically equal to ``Expected``. +----------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------- +cr_assert_str_neq(Actual, Unexpected, [Message, [Args...]]) ``Actual`` is not lexicographically equal to ``Unexpected``. +----------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------- +cr_assert_str_lt(Actual, Reference, [Message, [Args...]]) ``Actual`` is lexicographically less than ``Reference``. +----------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------- +cr_assert_str_leq(Actual, Reference, [Message, [Args...]]) ``Actual`` is lexicographically less or equal to ``Reference``. +----------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------- +cr_assert_str_gt(Actual, Reference, [Message, [Args...]]) ``Actual`` is lexicographically greater than ``Reference``. +----------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------- +cr_assert_str_geq(Actual, Reference, [Message, [Args...]]) ``Actual`` is lexicographically greater or equal to ``Reference``. +=========================================================== =================================================================== =========================================== + +Array Assertions +----------------- + +=========================================================================== =========================================================================== =========================================== +Macro Passes if and only if Notes +=========================================================================== =========================================================================== =========================================== +cr_assert_arr_eq(Actual, Expected, [Message, [Args...]]) ``Actual`` is byte-to-byte equal to ``Expected``. This should not be used on struct arrays, + consider using ``cr_assert_arr_eq_cmp`` + instead. +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_arr_neq(Actual, Unexpected, [Message, [Args...]]) ``Actual`` is not byte-to-byte equal to ``Unexpected``. This should not be used on struct arrays, + consider using ``cr_assert_arr_neq_cmp`` + instead. +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_arr_eq_cmp(Actual, Expected, Size, Cmp, [Message, [Args...]]) ``Actual`` is comparatively equal to ``Expected`` Only available in C++ and GNU C99 +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_arr_neq_cmp(Actual, Unexpected, Size, Cmp, [Message, [Args...]]) ``Actual`` is not comparatively equal to ``Expected`` Only available in C++ and GNU C99 +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_arr_lt_cmp(Actual, Reference, Size, Cmp, [Message, [Args...]]) ``Actual`` is comparatively less than ``Reference`` Only available in C++ and GNU C99 +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_arr_leq_cmp(Actual, Reference, Size, Cmp, [Message, [Args...]]) ``Actual`` is comparatively less or equal to ``Reference`` Only available in C++ and GNU C99 +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_arr_gt_cmp(Actual, Reference, Size, Cmp, [Message, [Args...]]) ``Actual`` is comparatively greater than ``Reference`` Only available in C++ and GNU C99 +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_arr_geq_cmp(Actual, Reference, Size, Cmp, [Message, [Args...]]) ``Actual`` is comparatively greater or equal to ``Reference`` Only available in C++ and GNU C99 +=========================================================================== =========================================================================== =========================================== + +Exception Assertions +-------------------- + +The following assertion macros are only defined for C++. + +=========================================================================== =========================================================================== =========================================== +Macro Passes if and only if Notes +=========================================================================== =========================================================================== =========================================== +cr_assert_throw(Statement, Exception, [Message, [Args...]]) ``Statement`` throws an instance of ``Exception``. +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_no_throw(Statement, Exception, [Message, [Args...]]) ``Statement`` does not throws an instance of ``Exception``. +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_any_throw(Statement, [Message, [Args...]]) ``Statement`` throws any kind of exception. +--------------------------------------------------------------------------- --------------------------------------------------------------------------- ------------------------------------------- +cr_assert_none_throw(Statement, [Message, [Args...]]) ``Statement`` does not throw any exception. +=========================================================================== =========================================================================== =========================================== + +File Assertions +--------------- + +=============================================================================== ============================================================================ =========================================== +Macro Passes if and only if Notes +=============================================================================== ============================================================================ =========================================== +cr_assert_file_contents_eq_str(File, ExpectedContents, [Message, [Args...]]) The contents of ``File`` are equal to the string ``ExpectedContents``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_file_contents_neq_str(File, ExpectedContents, [Message, [Args...]]) The contents of ``File`` are not equal to the string ``ExpectedContents``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stdout_eq_str(ExpectedContents, [Message, [Args...]]) The contents of ``stdout`` are equal to the string ``ExpectedContents``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stdout_neq_str(ExpectedContents, [Message, [Args...]]) The contents of ``stdout`` are not equal to the string ``ExpectedContents``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stderr_eq_str(ExpectedContents, [Message, [Args...]]) The contents of ``stderr`` are equal to the string ``ExpectedContents``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stderr_neq_str(ExpectedContents, [Message, [Args...]]) The contents of ``stderr`` are not equal to the string ``ExpectedContents``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_file_contents_eq(File, RefFile, [Message, [Args...]]) The contents of ``File`` are equal to the contents of ``RefFile``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_file_contents_neq(File, RefFile, [Message, [Args...]]) The contents of ``File`` are not equal to the contents of ``RefFile``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stdout_eq(RefFile, [Message, [Args...]]) The contents of ``stdout`` are equal to the contents of ``RefFile``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stdout_neq(RefFile, [Message, [Args...]]) The contents of ``stdout`` are not equal to the contents of ``RefFile``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stderr_eq(RefFile, [Message, [Args...]]) The contents of ``stderr`` are equal to the contents of ``RefFile``. +------------------------------------------------------------------------------- ---------------------------------------------------------------------------- ------------------------------------------- +cr_assert_stderr_neq(RefFile, [Message, [Args...]]) The contents of ``stderr`` are not equal to the contents of ``RefFile``. +=============================================================================== ============================================================================ =========================================== + diff --git a/doc/hooks.rst b/doc/hooks.rst index a9b8bd60..ff334a63 100644 --- a/doc/hooks.rst +++ b/doc/hooks.rst @@ -31,6 +31,7 @@ The flow of the test process goes as follows: #. ``PRE_INIT``: occurs before a test is initialized. #. ``PRE_TEST``: occurs after the test initialization, but before the test is run. #. ``ASSERT``: occurs when an assertion is hit +#. ``THEORY_FAIL``: occurs when a theory iteration fails. #. ``TEST_CRASH``: occurs when a test crashes unexpectedly. #. ``POST_TEST``: occurs after a test ends, but before the test finalization. #. ``POST_FINI``: occurs after a test finalization. @@ -50,6 +51,7 @@ Valid types for each phases are: * ``struct criterion_suite_set *`` for ``PRE_SUITE``. * ``struct criterion_test *`` for ``PRE_INIT`` and ``PRE_TEST``. * ``struct criterion_assert_stats *`` for ``ASSERT``. +* ``struct criterion_theory_stats *`` for ``THEORY_FAIL``. * ``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``. diff --git a/doc/index.rst b/doc/index.rst index dd79dd7e..56fbdf45 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -7,7 +7,9 @@ Criterion intro setup starter + assert hooks env + theories internal faq diff --git a/doc/internal.rst b/doc/internal.rst index 8c942376..69b5f0ad 100644 --- a/doc/internal.rst +++ b/doc/internal.rst @@ -10,6 +10,13 @@ can define your own main function. Configuring the test runner ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +First and foremost, you need to generate the test set; this is done by calling +``criterion_initialize()``. The function returns a ``struct criterion_test_set *``, +that you need to pass to ``criterion_run_all_tests`` later on. + +At the very end of your main, you also need to call ``criterion_finalize`` with +the test set as parameter to free any ressources initialized by criterion earlier. + 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). @@ -34,12 +41,40 @@ fail_fast bool True iff the test runner pattern const char * The pattern of the tests that should be executed =================== ================================== ============================================================== +if you want criterion to provide its own default CLI parameters and environment +variables handling, you can also call ``criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg)`` +with the proper ``argc/argv``. ``handle_unknown_arg``, if set to true, is here +to tell criterion to print its usage when an unknown CLI parameter is encountered. +If you want to add your own parameters, you should set it to false. + +The function returns 0 if the main should exit immediately, and 1 if it should +continue. + 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. +Example main +~~~~~~~~~~~~ + +.. code-block:: c + + #include + + int main(int argc, char *argv[]) { + struct criterion_test_set *tests = criterion_initialize(); + + if (!criterion_handle_args(argc, argv, true)) + return 0; + + int result = !criterion_run_all_tests(set); + + criterion_finalize(set); + return result; + } + Implementing your own output provider ------------------------------------- diff --git a/doc/setup.rst b/doc/setup.rst index 507fc810..b4001e2b 100644 --- a/doc/setup.rst +++ b/doc/setup.rst @@ -4,28 +4,39 @@ Setup Prerequisites ------------- -Currently, this library only works under \*nix systems. +The library is supported on Linux, OS X, FreeBSD, and Windows. -To compile the static library and its dependencies, GCC 4.6+ is needed. +The following compilers are supported to compile both the library and the tests: +* GCC 4.9+ +* Clang 3.4+ +* MSVC 14+ (Included in Visual Studio 2015 or later) -To use the static library, any GNU-C compatible compiler will suffice -(GCC, Clang/LLVM, ICC, MinGW-GCC, ...). +Building from source +-------------------- -Installation ------------- +.. code-block:: bash + + $ mkdir build + $ cd build + $ cmake .. + $ cmake --build . + +Installing the library and language files (Linux, OS X, FreeBSD) +---------------------------------------------------------------- + +From the build directory created above, run with an elevated shell: .. code-block:: bash - $ git clone https://github.com/Snaipe/Criterion.git && cd Criterion - $ LOCAL_INSTALL=/usr .ci/install-libcsptr.sh - $ mkdir build && cd $_ && cmake -DCMAKE_INSTALL_PATH=/usr .. - $ make && sudo make install + $ make install Usage ----- -Given a test file named test.c, compile it with `-lcriterion`: +To compile your tests with Criterion, you need to make sure to: -.. code-block:: bash +1. Add the include directory to the header search path +2. Install the library to your library search path +3. Link Criterion to your executable. - $ gcc -o test test.c -lcriterion +This should be all you need. diff --git a/doc/starter.rst b/doc/starter.rst index 106c7d19..4e329122 100644 --- a/doc/starter.rst +++ b/doc/starter.rst @@ -48,45 +48,8 @@ parameter, and an optional failure message: cr_assert(strlen("") == 0); } -On top of those, more assertions are available for common operations: - -* ``cr_assert_null(Ptr, [Message])``: passes if Ptr is NULL. -* ``cr_assert_eq(Actual, Expected, [Message])``: passes if Actual == Expected. -* ``cr_assert_lt(Actual, Expected, [Message])``: passes if Actual < Expected. -* ``cr_assert_leq(Actual, Expected, [Message])``: passes if Actual <= Expected. -* ``cr_assert_gt(Actual, Expected, [Message])``: passes if Actual > Expected. -* ``cr_assert_geq(Actual, Expected, [Message])``: passes if Actual >= Expected. -* ``cr_assert_float_eq(Actual, Expected, Epsilon, [Message])``: - passes if Actual == Expected with an error of Epsilon. -* ``cr_assert_arrays_eq(Actual, Expected, Size, [Message])``: - passes if all elements of Actual (from 0 to Size - 1) are equals to those - of Expected. -* ``cr_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: - -* ``cr_assert_strings_eq(Actual, Expected, [Message])`` -* ``cr_assert_strings_lt(Actual, Expected, [Message])`` -* ``cr_assert_strings_leq(Actual, Expected, [Message])`` -* ``cr_assert_strings_gt(Actual, Expected, [Message])`` -* ``cr_assert_strings_geq(Actual, Expected, [Message])`` - -And some assertions have a logical negative counterpart: - -* ``cr_assert_not(Condition, [Message])`` -* ``cr_assert_not_null(Ptr, [Message])`` -* ``cr_assert_neq(Actual, Unexpected, [Message])`` -* ``cr_assert_float_neq(Actual, Unexpected, Epsilon, [Message])`` -* ``cr_assert_strings_neq(Actual, Unexpected, [Message])`` -* ``cr_assert_arrays_neq(Actual, Unexpected, Size, [Message])`` -* ``cr_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. +On top of those, more assertions are available for common operations. See +:ref:`assertions-ref` for a complete list. Configuring tests ----------------- @@ -176,6 +139,8 @@ See the `windows exception reference`_ for more details on each exception. .. _windows exception reference: https://msdn.microsoft.com/en-us/library/windows/desktop/ms679356(v=vs.85).aspx +.. _test-config-ref: + Configuration reference ~~~~~~~~~~~~~~~~~~~~~~~ @@ -194,6 +159,8 @@ Parameter Type Description .disabled bool Disables the test. ------------- --------------- -------------------------------------------------------------- .signal int Expect the test to raise the specified signal. +------------- --------------- -------------------------------------------------------------- +.exit_code int Expect the test to exit with the specified status. ============= =============== ============================================================== Setting up suite-wise configuration diff --git a/doc/theories.rst b/doc/theories.rst new file mode 100644 index 00000000..9619affe --- /dev/null +++ b/doc/theories.rst @@ -0,0 +1,242 @@ +Using theories +============== + +`Theories`_ are a powerful tool for test-driven development, allowing you +to test a specific behaviour against all permutations of a set of user-defined +parameters known as "data points". + +.. _Theories: http://web.archive.org/web/20110608210825/http://shareandenjoy.saff.net/tdd-specifications.pdf + +Adding theories +--------------- + +Adding theories is done by defining data points and a theory function: + +.. code-block:: c + + #include + + TheoryDataPoints(suite_name, test_name) = { + DataPoints(Type0, val0, val1, val2, ..., valN), + DataPoints(Type1, val0, val1, val2, ..., valN), + ... + DataPoints(TypeN, val0, val1, val2, ..., valN), + } + + Theory((Type0 arg0, Type1 arg1, ..., TypeN argN), suite_name, test_name) { + } + +``suite_name`` and ``test_name`` are the identifiers of the test suite and +the test, respectively. These identifiers must follow the language +identifier format. + +``Type0/arg0`` through ``TypeN/argN`` are the parameter types and names of theory +theory function and are available in the body of the function. + +Datapoints are declared in the same number, type, and order than the parameters +inside the ``TheoryDataPoints`` macro, with the ``DataPoints`` macro. +Beware! It is undefined behaviour to not have a matching number and type of +theory parameters and datatypes. + +Each ``DataPoints`` must then specify the values that will be used for the +theory parameter it is linked to (``val0`` through ``valN``). + +Assertions and invariants +------------------------- + +You can use any ``cr_assert`` or ``cr_expect`` macro functions inside the body of a +theory function. + +Theory invariants are enforced through the ``cr_assume(Condition)`` macro function: +if ``Condition`` is false, then the current theory iteration aborts without +making the test fail. + +On top of those, more ``assume`` macro functions are available for common operations: + +======================================================= ==================================================== +Macro Description +======================================================= ==================================================== +``cr_assume_not(Condition)`` Assumes Condition is false. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_null(Ptr)`` Assumes Ptr is NULL. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_not_null(Ptr)`` Assumes Ptr is not NULL. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_eq(Actual, Expected)`` Assumes Actual == Expected. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_neq(Actual, Unexpected)`` Assumes Actual != Expected. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_lt(Actual, Expected)`` Assumes Actual < Expected. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_leq(Actual, Expected)`` Assumes Actual <= Expected. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_gt(Actual, Expected)`` Assumes Actual > Expected. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_geq(Actual, Expected)`` Assumes Actual >= Expected. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_float_eq(Actual, Expected, Epsilon)`` Assumes Actual == Expected with an error of Epsilon. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_float_neq(Actual, Unexpected, Epsilon)`` Assumes Actual != Expected with an error of Epsilon. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_strings_eq(Actual, Expected)`` Assumes Actual and Expected are the same string. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_strings_neq(Actual, Unexpected)`` Assumes Actual and Expected are not the same string. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_strings_lt(Actual, Expected)`` Assumes Actual is less than Expected + lexicographically. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_strings_leq(Actual, Expected)`` Assumes Actual is less or equal to Expected + lexicographically. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_strings_gt(Actual, Expected)`` Assumes Actual is greater than Expected + lexicographically. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_strings_geq(Actual, Expected)`` Assumes Actual is greater or equal to Expected + lexicographically. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_arrays_eq(Actual, Expected, Size)`` Assumes all elements of Actual (from 0 to Size - 1) + are equals to those of Expected. +------------------------------------------------------- ---------------------------------------------------- +``cr_assume_arrays_neq(Actual, Unexpected, Size)`` Assumes one or more elements of Actual (from 0 to + Size - 1) differs from their counterpart in Expected. +======================================================= ==================================================== + +Configuring theories +-------------------- + +Theories can optionally recieve configuration parameters to alter the behaviour +of the underlying test; as such, those parameters are the same ones as the ones +of the ``Test`` macro function (c.f. :ref:`test-config-ref`). + +Full sample & purpose of theories +--------------------------------- + +We will illustrate how useful theories are with a simple example using Criterion: + +The basics of theories +~~~~~~~~~~~~~~~~~~~~~~ + +Let us imagine that we want to test if the algebraic properties of integers, +and specifically concerning multiplication, are respected by the C language: + +.. code-block:: c + + int my_mul(int lhs, int rhs) { + return lhs * rhs; + } + +Now, we know that multiplication over integers is commutative, so we first test +that: + +.. code-block:: c + + #include + + Test(algebra, multiplication_is_commutative) { + cr_assert_eq(my_mul(2, 3), my_mul(3, 2)); + } + +However, this test is imperfect, because there is not enough triangulation to +insure that my_mul is indeed commutative. One might be tempted to add more +assertions on other values, but this will never be good enough: commutativity +should work for *any* pair of integers, not just an arbitrary set, but, to be +fair, you cannot just test this behaviour for every integer pair that exists. + +Theories purposely bridge these two issues by introducing the concept of +"data point" and by refactoring the repeating logic into a dedicated function: + +.. code-block:: c + + #include + + TheoryDataPoints(algebra, multiplication_is_commutative) = { + DataPoints(int, [...]), + DataPoints(int, [...]), + }; + + Theory((int lhs, int rhs), algebra, multiplication_is_commutative) { + cr_assert_eq(my_mul(lhs, rhs), my_mul(rhs, lhs)); + } + +As you can see, we refactored the assertion into a theory taking two unspecified +integers. + +We first define some data points in the same order and type the parameters have, +from left to right: the first ``DataPoints(int, ...)`` will define the set of values passed +to the ``int lhs`` parameter, and the second will define the one passed to ``int rhs``. + +Choosing the values of the data point is left to you, but we might as well use +"interesting" values: ``0``, ``-1``, ``1``, ``-2``, ``2``, ``INT_MAX``, and ``INT_MIN``: + +.. code-block:: c + + #include + + TheoryDataPoints(algebra, multiplication_is_commutative) = { + DataPoints(int, 0, -1, 1, -2, 2, INT_MAX, INT_MIN), + DataPoints(int, 0, -1, 1, -2, 2, INT_MAX, INT_MIN), + }; + +Using theory invariants +~~~~~~~~~~~~~~~~~~~~~~~ + +The second thing we can test on multiplication is that it is the inverse function +of division. Then, given the division operation: + +.. code-block:: c + + int my_div(int lhs, int rhs) { + return lhs / rhs; + } + +The associated theory is straight-forward: + +.. code-block:: c + + #include + + TheoryDataPoints(algebra, multiplication_is_inverse_of_division) = { + DataPoints(int, 0, -1, 1, -2, 2, INT_MAX, INT_MIN), + DataPoints(int, 0, -1, 1, -2, 2, INT_MAX, INT_MIN), + }; + + Theory((int lhs, int rhs), algebra, multiplication_is_inverse_of_division) { + cr_assert_eq(lhs, my_div(my_mul(lhs, rhs), rhs)); + } + +However, we do have a problem because you cannot have the theory function divide +by 0. For this purpose, we can ``assume`` than ``rhs`` will never be 0: + +.. code-block:: c + + Theory((int lhs, int rhs), algebra, multiplication_is_inverse_of_division) { + cr_assume(rhs != 0); + cr_assert_eq(lhs, my_div(my_mul(lhs, rhs), rhs)); + } + +``cr_assume`` will abort the current theory iteration if the condition is not +fulfiled. + +Running the test at that point will raise a big problem with the current +implementation of ``my_mul`` and ``my_div``: + +.. code-block:: none + + [----] theories.c:24: Assertion failed: (a) == (bad_div(bad_mul(a, b), b)) + [----] Theory algebra::multiplication_is_inverse_of_division failed with the following parameters: (2147483647, 2) + [----] theories.c:24: Assertion failed: (a) == (bad_div(bad_mul(a, b), b)) + [----] Theory algebra::multiplication_is_inverse_of_division failed with the following parameters: (-2147483648, 2) + [----] theories.c:24: Unexpected signal caught below this line! + [FAIL] algebra::multiplication_is_inverse_of_division: CRASH! + +The theory shows that ``my_div(my_mul(INT_MAX, 2), 2)`` and ``my_div(my_mul(INT_MIN, 2), 2)`` +does not respect the properties for multiplication: it happens that the +behaviour of these two functions is undefined because the operation overflows. + +Similarly, the test crashes at the end; debugging shows that the source of the +crash is the divison of INT_MAX by -1, which is undefined. + +Fixing this is as easy as changing the prototypes of ``my_mul`` and ``my_div`` +to operate on ``long long`` rather than ``int``. + + diff --git a/include/criterion/abort.h b/include/criterion/abort.h index f20e3003..026289f2 100644 --- a/include/criterion/abort.h +++ b/include/criterion/abort.h @@ -26,6 +26,11 @@ # include "common.h" -NORETURN void criterion_abort_test(void); +CR_BEGIN_C_API + +CR_API NORETURN void criterion_abort_test(void); +CR_INLINE static void criterion_continue_test(void) {} + +CR_END_C_API #endif /* !CRITERION_ABORT_H_ */ diff --git a/include/criterion/asprintf-compat.h b/include/criterion/asprintf-compat.h new file mode 100644 index 00000000..6f78efdd --- /dev/null +++ b/include/criterion/asprintf-compat.h @@ -0,0 +1,43 @@ +/* + * 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_ASPRINTF_COMPAT_H_ +# define CRITERION_ASPRINTF_COMPAT_H_ + +# ifdef __cplusplus +# include +# else +# include +# endif + +# include "common.h" + +CR_BEGIN_C_API + +FORMAT(printf, 2, 3) +CR_API int cr_asprintf(char **strp, const char *fmt, ...); +CR_API int cr_vasprintf(char **strp, const char *fmt, va_list ap); + +CR_END_C_API + +#endif /* !CRITERION_ASPRINTF_COMPAT_H_ */ diff --git a/include/criterion/assert.h b/include/criterion/assert.h index 5e7f6168..f29860ed 100644 --- a/include/criterion/assert.h +++ b/include/criterion/assert.h @@ -24,263 +24,598 @@ #ifndef CRITERION_ASSERT_H_ # define CRITERION_ASSERT_H_ -# include -# include -# include +# include "preprocess.h" +# include "asprintf-compat.h" + +# ifdef __cplusplus +# include +# include +# include +# else +# include +# include +# include +# endif +# include "designated-initializer-compat.h" # include "types.h" # include "stats.h" # include "hooks.h" # include "event.h" # include "abort.h" -enum criterion_assert_kind { - NORMAL, - FATAL -}; - struct criterion_assert_args { const char *msg; - const char *default_msg; int sentinel_; -}; -# define cr_assert_impl(Kind, Condition, ...) \ - do { \ - struct criterion_assert_args args = { \ - __VA_ARGS__ \ - }; \ - int passed = !!(Condition); \ - struct criterion_assert_stats stat = { \ - .kind = (Kind), \ - .condition = #Condition, \ - .message = args.msg ? args.msg \ - : (args.default_msg ? args.default_msg : ""), \ - .passed = passed, \ - .file = __FILE__, \ - .line = __LINE__, \ - }; \ - send_event(ASSERT, &stat, sizeof (stat)); \ - if (!passed && (Kind) == FATAL) \ - criterion_abort_test(); \ - } while (0) - -// Common asserts +#ifdef __cplusplus + constexpr criterion_assert_args(const char *msg) : msg(msg), sentinel_(0) {} + constexpr criterion_assert_args(const char *msg, int sentinel_) : msg(msg), sentinel_(sentinel_) {} +#endif +}; -# define cr_abort_test(Message) \ - cr_assert(0, \ - .default_msg = "The conditions for this test were not met.", \ - .msg = (Message) \ - ) +// Do NOT reorder unless you want to break the ABI +enum criterion_assert_messages { + CRITERION_ASSERT_MSG_FAIL, + CRITERION_ASSERT_MSG_EXPR_FALSE, + CRITERION_ASSERT_MSG_EXPR_AS_STRINGS_FALSE, + CRITERION_ASSERT_MSG_IS_NULL, + CRITERION_ASSERT_MSG_IS_NOT_NULL, + CRITERION_ASSERT_MSG_IS_EMPTY, + CRITERION_ASSERT_MSG_IS_NOT_EMPTY, + CRITERION_ASSERT_MSG_FILE_STR_MATCH, + CRITERION_ASSERT_MSG_FILE_MATCH, + CRITERION_ASSERT_MSG_THROW, + CRITERION_ASSERT_MSG_NO_THROW, + CRITERION_ASSERT_MSG_ANY_THROW, + CRITERION_ASSERT_MSG_NONE_THROW, +}; -# define cr_assert(...) cr_assert_(__VA_ARGS__, .sentinel_ = 0) +CR_BEGIN_C_API -# define cr_expect(...) cr_expect_(__VA_ARGS__, .sentinel_ = 0) +CR_API char *translate_assert_msg(int msg_index, ...); -# define cr_assert_(Condition, ...) cr_assert_impl(FATAL, Condition, __VA_ARGS__) -# define cr_expect_(Condition, ...) cr_assert_impl(NORMAL, Condition, __VA_ARGS__) +CR_END_C_API -# define cr_assert_not(...) cr_assert_not_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_not(...) cr_expect_not_(__VA_ARGS__, .sentinel_ = 0) +# define CR_GET_CONDITION(Condition, ...) Condition +# define CR_GET_CONDITION_STR(Condition, ...) #Condition +# define CR_VA_SKIP(_, ...) __VA_ARGS__ -# define cr_assert_not_(Condition, ...) \ - cr_assert_impl(FATAL, !(Condition), __VA_ARGS__) -# define cr_expect_not_(Condition, ...) \ - cr_expect_impl(NORMAL, !(Condition), __VA_ARGS__) +# ifdef __cplusplus +# define CR_STDN std:: +# else +# define CR_STDN +# endif -// Native asserts +# define CR_TRANSLATE_DEF_MSG__(Arg) \ + CR_IDENTITY Arg + +# define CR_TRANSLATE_DEF_MSG_(...) \ + CR_EXPAND(translate_assert_msg( \ + CR_VA_HEAD(__VA_ARGS__), \ + "" CR_TRANSLATE_DEF_MSG__(CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__))) \ + )) + +# define CR_INIT_STATS_(BufSize, MsgVar, ...) CR_EXPAND( \ + do { \ + char *def_msg = CR_EXPAND(CR_TRANSLATE_DEF_MSG_(__VA_ARGS__)); \ + char *formatted_msg = NULL; \ + int msglen = cr_asprintf(&formatted_msg, \ + "" CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))); \ + if (formatted_msg && *formatted_msg) { \ + MsgVar = formatted_msg; \ + CR_STDN free(def_msg); \ + } else { \ + MsgVar = def_msg; \ + msglen = strlen(def_msg); \ + CR_STDN free(formatted_msg); \ + } \ + \ + BufSize = sizeof(struct criterion_assert_stats) \ + + sizeof (size_t) + msglen + 1; \ + \ + char *buf = (char*) CR_STDN malloc(BufSize); \ + stat = (struct criterion_assert_stats*) buf; \ + CR_STDN memset(buf, 0, sizeof (struct criterion_assert_stats)); \ + buf += sizeof (struct criterion_assert_stats); \ + *((size_t*) buf) = msglen + 1; \ + buf += sizeof (size_t); \ + CR_STDN strcpy(buf, MsgVar); \ + CR_STDN free(MsgVar); \ + } while (0)) + +# define CR_FAIL_ABORT_ criterion_abort_test +# define CR_FAIL_CONTINUES_ criterion_continue_test -# define cr_assert_op_(Op, Actual, Expected, ...) \ - cr_assert_impl(FATAL, (Actual) Op (Expected), __VA_ARGS__) -# define cr_expect_op_(Op, Actual, Expected, ...) \ - cr_assert_impl(NORMAL, (Actual) Op (Expected), __VA_ARGS__) +# ifdef __GNUC__ +// We disable the format-zero-length warning because we use the validity of +// asprintf(out, "") for empty assertion messages +# pragma GCC diagnostic ignored "-Wformat-zero-length" +# endif -# define cr_assert_eq(...) cr_assert_op_(==, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_eq(...) cr_expect_op_(==, __VA_ARGS__, .sentinel_ = 0) +# define cr_assert_impl(Fail, Condition, ...) \ + do { \ + bool passed = !!(Condition); \ + \ + char *msg = NULL; \ + size_t bufsize; \ + \ + struct criterion_assert_stats *stat; \ + CR_EXPAND(CR_INIT_STATS_(bufsize, msg, CR_VA_TAIL(__VA_ARGS__))); \ + stat->passed = passed; \ + stat->file = __FILE__; \ + stat->line = __LINE__; \ + \ + send_event(ASSERT, stat, bufsize); \ + CR_STDN free(stat); \ + \ + if (!passed) \ + Fail(); \ + } while (0) -# define cr_assert_neq(...) cr_assert_op_(!=, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_neq(...) cr_expect_op_(!=, __VA_ARGS__, .sentinel_ = 0) +// Base assertions -# define cr_assert_lt(...) cr_assert_op_(<, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_lt(...) cr_expect_op_(<, __VA_ARGS__, .sentinel_ = 0) +# define cr_fail(Fail, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + 0, \ + dummy, \ + CRITERION_ASSERT_MSG_FAIL, \ + (), \ + __VA_ARGS__ \ + )) + +# define cr_assert_fail(...) CR_EXPAND(cr_fail(CR_FAIL_ABORT_, __VA_ARGS__)) +# define cr_expect_fail(...) CR_EXPAND(cr_fail(CR_FAIL_CONTINUES_, __VA_ARGS__)) + +# define cr_assert(...) \ + CR_EXPAND(cr_assert_impl( \ + CR_FAIL_ABORT_, \ + CR_VA_HEAD(__VA_ARGS__), \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR(CR_VA_HEAD(__VA_ARGS__))), \ + CR_VA_TAIL(__VA_ARGS__) \ + )) + +# define cr_expect(...) \ + CR_EXPAND(cr_assert_impl( \ + CR_FAIL_CONTINUES_, \ + CR_VA_HEAD(__VA_ARGS__), \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR(CR_VA_HEAD(__VA_ARGS__))), \ + CR_VA_TAIL(__VA_ARGS__) \ + )) + +# define cr_assert_not(...) \ + CR_EXPAND(cr_assert_impl( \ + CR_FAIL_ABORT_, \ + !(CR_VA_HEAD(__VA_ARGS__)), \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR(!(CR_VA_HEAD(__VA_ARGS__)))), \ + CR_VA_TAIL(__VA_ARGS__) \ + )) + +# define cr_expect_not(...) \ + CR_EXPAND(cr_assert_impl( \ + CR_FAIL_CONTINUES_, \ + !(CR_VA_HEAD(__VA_ARGS__)), \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR(!(CR_VA_HEAD(__VA_ARGS__)))), \ + CR_VA_TAIL(__VA_ARGS__) \ + )) + +// Common binary assertions + +# define cr_assert_op_(Fail, Op, Actual, Expected, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + (Actual) Op (Expected), \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR((Actual) Op (Expected))), \ + __VA_ARGS__ \ + )) + +# define cr_assert_op_va_(Fail, Op, ...) \ + CR_EXPAND(cr_assert_op_( \ + Fail, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)) \ + )) + +# define cr_assert_eq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_ABORT_, ==, __VA_ARGS__)) +# define cr_expect_eq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_CONTINUES_, ==, __VA_ARGS__)) + +# define cr_assert_neq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_ABORT_, !=, __VA_ARGS__)) +# define cr_expect_neq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_CONTINUES_, !=, __VA_ARGS__)) + +# define cr_assert_lt(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_ABORT_, <, __VA_ARGS__)) +# define cr_expect_lt(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_CONTINUES_, <, __VA_ARGS__)) + +# define cr_assert_leq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_ABORT_, <=, __VA_ARGS__)) +# define cr_expect_leq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_CONTINUES_, <=, __VA_ARGS__)) + +# define cr_assert_gt(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_ABORT_, >, __VA_ARGS__)) +# define cr_expect_gt(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_CONTINUES_, >, __VA_ARGS__)) + +# define cr_assert_geq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_ABORT_, >=, __VA_ARGS__)) +# define cr_expect_geq(...) CR_EXPAND(cr_assert_op_va_(CR_FAIL_CONTINUES_, >=, __VA_ARGS__)) + +// Common unary assertions + +# define cr_assert_null_op_(Fail, Op, Msg, Value, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + (Value) Op NULL, \ + dummy, \ + Msg, \ + (CR_STR(Value)), \ + __VA_ARGS__ \ + )) + +# define cr_assert_null_op_va_(Fail, Op, Msg, ...) \ + CR_EXPAND(cr_assert_null_op_( \ + Fail, \ + Op, \ + Msg, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_TAIL(__VA_ARGS__) \ + )) + +# define cr_assert_null(...) CR_EXPAND(cr_assert_null_op_va_(CR_FAIL_ABORT_, ==, CRITERION_ASSERT_MSG_IS_NOT_NULL, __VA_ARGS__)) +# define cr_expect_null(...) CR_EXPAND(cr_assert_null_op_va_(CR_FAIL_CONTINUES_, ==, CRITERION_ASSERT_MSG_IS_NOT_NULL, __VA_ARGS__)) + +# define cr_assert_not_null(...) CR_EXPAND(cr_assert_null_op_va_(CR_FAIL_ABORT_, !=, CRITERION_ASSERT_MSG_IS_NULL, __VA_ARGS__)) +# define cr_expect_not_null(...) CR_EXPAND(cr_assert_null_op_va_(CR_FAIL_CONTINUES_, !=, CRITERION_ASSERT_MSG_IS_NULL, __VA_ARGS__)) + +// Floating-point assertions + +# define cr_assert_float_eq_op_(Actual, Expected, Epsilon) \ + (Expected) - (Actual) <= (Epsilon) && (Actual) - (Expected) <= (Epsilon) + +# define cr_assert_float_neq_op_(Actual, Expected, Epsilon) \ + (Expected) - (Actual) > (Epsilon) || (Actual) - (Expected) > (Epsilon) + +# define cr_assert_float_op_(Fail, Op, Actual, Expected, Epsilon, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + Op(Actual, Expected, Epsilon), \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR(Op(Actual, Expected, Epsilon))), \ + __VA_ARGS__ \ + )) + +# define cr_assert_float_op_va_(Fail, Op, ...) \ + CR_EXPAND(cr_assert_float_op_( \ + Fail, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_HEAD(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))), \ + CR_VA_TAIL(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))) \ + )) + +# define cr_assert_float_eq(...) CR_EXPAND(cr_assert_float_op_va_(CR_FAIL_ABORT_, cr_assert_float_eq_op_, __VA_ARGS__)) +# define cr_expect_float_eq(...) CR_EXPAND(cr_assert_float_op_va_(CR_FAIL_CONTINUES_, cr_assert_float_eq_op_, __VA_ARGS__)) + +# define cr_assert_float_neq(...) CR_EXPAND(cr_assert_float_op_va_(CR_FAIL_ABORT_, cr_assert_float_neq_op_, __VA_ARGS__)) +# define cr_expect_float_neq(...) CR_EXPAND(cr_assert_float_op_va_(CR_FAIL_CONTINUES_, cr_assert_float_neq_op_, __VA_ARGS__)) + +// String assertions + +# define cr_assert_str_op_empty_(Fail, Op, Msg, Value, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + (Value)[0] Op '\0', \ + dummy, \ + Msg, \ + (CR_STR(Value)), \ + __VA_ARGS__ \ + )) + +# define cr_assert_str_op_empty_va_(Fail, Op, Msg, ...) \ + CR_EXPAND(cr_assert_str_op_empty_( \ + Fail, \ + Op, \ + Msg, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_TAIL(__VA_ARGS__) \ + )) + +# define cr_assert_str_empty(...) CR_EXPAND(cr_assert_str_op_empty_va_(CR_FAIL_ABORT_, ==, CRITERION_ASSERT_MSG_IS_NOT_EMPTY, __VA_ARGS__)) +# define cr_expect_str_empty(...) CR_EXPAND(cr_assert_str_op_empty_va_(CR_FAIL_CONTINUES_, ==, CRITERION_ASSERT_MSG_IS_NOT_EMPTY, __VA_ARGS__)) + +# define cr_assert_str_not_empty(...) CR_EXPAND(cr_assert_str_op_empty_va_(CR_FAIL_ABORT_, !=, CRITERION_ASSERT_MSG_IS_EMPTY, __VA_ARGS__)) +# define cr_expect_str_not_empty(...) CR_EXPAND(cr_assert_str_op_empty_va_(CR_FAIL_CONTINUES_, !=, CRITERION_ASSERT_MSG_IS_EMPTY, __VA_ARGS__)) + +# define cr_assert_str_op_(Fail, Op, Actual, Expected, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + CR_STDN strcmp((Actual), (Expected)) Op 0, \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_AS_STRINGS_FALSE, \ + (CR_STR((Actual) Op (Expected))), \ + __VA_ARGS__ \ + )) + +# define cr_assert_str_op_va_(Fail, Op, ...) \ + CR_EXPAND(cr_assert_str_op_( \ + Fail, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)) \ + )) + +# define cr_assert_str_eq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_ABORT_, ==, __VA_ARGS__)) +# define cr_expect_str_eq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_CONTINUES_, ==, __VA_ARGS__)) + +# define cr_assert_str_neq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_ABORT_, !=, __VA_ARGS__)) +# define cr_expect_str_neq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_CONTINUES_, !=, __VA_ARGS__)) + +# define cr_assert_str_lt(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_ABORT_, <, __VA_ARGS__)) +# define cr_expect_str_lt(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_CONTINUES_, <, __VA_ARGS__)) + +# define cr_assert_str_leq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_ABORT_, <=, __VA_ARGS__)) +# define cr_expect_str_leq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_CONTINUES_, <=, __VA_ARGS__)) + +# define cr_assert_str_gt(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_ABORT_, >, __VA_ARGS__)) +# define cr_expect_str_gt(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_CONTINUES_, >, __VA_ARGS__)) + +# define cr_assert_str_geq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_ABORT_, >=, __VA_ARGS__)) +# define cr_expect_str_geq(...) CR_EXPAND(cr_assert_str_op_va_(CR_FAIL_CONTINUES_, >=, __VA_ARGS__)) + +// Array assertions + +# define cr_assert_mem_op_(Fail, Op, Actual, Expected, Size, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + CR_STDN memcmp((Actual), (Expected), (Size)) Op 0, \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR((Actual)[0 .. Size] Op (Expected)[0 .. Size])), \ + __VA_ARGS__ \ + )) + +# define cr_assert_mem_op_va_(Fail, Op, ...) \ + CR_EXPAND(cr_assert_mem_op_( \ + Fail, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_HEAD(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))), \ + CR_VA_TAIL(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))) \ + )) + +# define cr_assert_arr_eq(...) CR_EXPAND(cr_assert_mem_op_va_(CR_FAIL_ABORT_, ==, __VA_ARGS__)) +# define cr_expect_arr_eq(...) CR_EXPAND(cr_assert_mem_op_va_(CR_FAIL_CONTINUES_, ==, __VA_ARGS__)) + +# define cr_assert_arr_neq(...) CR_EXPAND(cr_assert_mem_op_va_(CR_FAIL_ABORT_, !=, __VA_ARGS__)) +# define cr_expect_arr_neq(...) CR_EXPAND(cr_assert_mem_op_va_(CR_FAIL_CONTINUES_, !=, __VA_ARGS__)) + +// Safe array comparison assertions + +# if defined(__GNUC__) || defined(__cplusplus) + +# ifdef __cplusplus +# define CR_ARR_COMPARE_(A, B, Size, Cmp, Result) \ + int Result = std::lexicographical_compare((A), (A) + Size, (B), (B) + Size, Cmp) +# else +# define CR_ARR_COMPARE_(A, B, Size, Cmp, Result) \ + __typeof__(&(A)[0]) first = (A); \ + __typeof__(&(B)[0]) second = (B); \ + int Result = 0; \ + size_t i, size; \ + for (i = 0, size = (Size); !Result && i < size; ++i) \ + Result = Cmp(first + i, second + i) +# endif + +# define cr_assert_arr_op_cmp_(Fail, Op, Actual, Expected, Size, Cmp, ...) \ + do { \ + CR_ARR_COMPARE_(Actual, Expected, Size, Cmp, order); \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + order Op 0, \ + dummy, \ + CRITERION_ASSERT_MSG_EXPR_FALSE, \ + (CR_STR((Actual)[0 .. Size] Op (Expected)[0 .. Size])), \ + __VA_ARGS__ \ + )); \ + } while (0) -# define cr_assert_gt(...) cr_assert_op_(>, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_gt(...) cr_expect_op_(>, __VA_ARGS__, .sentinel_ = 0) +# define cr_assert_arr_op_cmp_va_(Fail, Op, ...) \ + CR_EXPAND(cr_assert_arr_op_cmp_( \ + Fail, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_HEAD(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))), \ + CR_VA_HEAD(CR_VA_TAIL(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)))), \ + CR_VA_TAIL(CR_VA_TAIL(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)))) \ + )) -# define cr_assert_leq(...) cr_assert_op_(<=, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_leq(...) cr_expect_op_(<=, __VA_ARGS__, .sentinel_ = 0) +# define cr_assert_arr_eq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_ABORT_, ==, __VA_ARGS__)) +# define cr_expect_arr_eq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_CONTINUES_, ==, __VA_ARGS__)) -# define cr_assert_geq(...) cr_assert_op_(>=, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_geq(...) cr_expect_op_(>=, __VA_ARGS__, .sentinel_ = 0) +# define cr_assert_arr_neq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_ABORT_, !=, __VA_ARGS__)) +# define cr_expect_arr_neq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_CONTINUES_, !=, __VA_ARGS__)) -# define cr_assert_null_(Value, ...) \ - cr_assert_impl(FATAL, (Value) == NULL, __VA_ARGS__) -# define cr_expect_null_(Value, ...) \ - cr_assert_impl(NORMAL, (Value) == NULL, __VA_ARGS__) +# define cr_assert_arr_lt_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_ABORT_, <, __VA_ARGS__)) +# define cr_expect_arr_lt_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_CONTINUES_, <, __VA_ARGS__)) -# define cr_assert_null(...) cr_assert_null_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_null(...) cr_expect_null_(__VA_ARGS__, .sentinel_ = 0) +# define cr_assert_arr_leq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_ABORT_, <=, __VA_ARGS__)) +# define cr_expect_arr_leq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_CONTINUES_, <=, __VA_ARGS__)) -# define cr_assert_not_null_(Value, ...) \ - cr_assert_impl(FATAL, (Value) != NULL, __VA_ARGS__) -# define cr_expect_not_null_(Value, ...) \ - cr_assert_impl(NORMAL, (Value) != NULL, __VA_ARGS__) +# define cr_assert_arr_gt_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_ABORT_, >, __VA_ARGS__)) +# define cr_expect_arr_gt_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_CONTINUES_, >, __VA_ARGS__)) -# define cr_assert_not_null(...) cr_assert_not_null_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_not_null(...) cr_expect_not_null_(__VA_ARGS__, .sentinel_ = 0) +# define cr_assert_arr_geq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_ABORT_, >=, __VA_ARGS__)) +# define cr_expect_arr_geq_cmp(...) CR_EXPAND(cr_assert_arr_op_cmp_va_(CR_FAIL_CONTINUES_, >=, __VA_ARGS__)) -// Floating-point asserts +# else -# define cr_assert_float_eq(...) \ - cr_assert_float_eq_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_float_eq(...) \ - cr_expect_float_eq_(__VA_ARGS__, .sentinel_ = 0) +# define CRITERION_GNUC_WARN__(Msg) \ + _Pragma(#Msg) -# define cr_assert_float_eq_(Actual, Expected, Epsilon, ...) \ - cr_assert_impl(FATAL, (Expected) - (Actual) <= (Epsilon) \ - && (Actual) - (Expected) <= (Epsilon), \ - __VA_ARGS__) -# define cr_expect_float_eq_(Actual, Expected, Epsilon, ...) \ - cr_assert_impl(NORMAL, (Expected) - (Actual) <= (Epsilon) \ - && (Actual) - (Expected) <= (Epsilon), \ - __VA_ARGS__) +# define CRITERION_GNUC_WARN_(Name) CRITERION_GNUC_WARN__( \ + message \ + "The `" #Name "` macro is only available on GNU C compilers." \ + ) -# define cr_assert_float_neq(...) \ - cr_assert_float_neq_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_float_neq(...) \ - cr_expect_float_neq_(__VA_ARGS__, .sentinel_ = 0) +# define cr_assert_arr_eq_cmp(...) CRITERION_GNUC_WARN_(cr_assert_arr_eq_cmp) CR_NOOP +# define cr_expect_arr_eq_cmp(...) CRITERION_GNUC_WARN_(cr_expect_arr_eq_cmp) CR_NOOP -# define cr_assert_float_neq_(Actual, Expected, Epsilon, ...) \ - cr_assert_impl(FATAL, (Expected) - (Actual) > (Epsilon) \ - || (Actual) - (Expected) > (Epsilon), \ - __VA_ARGS__) -# define cr_expect_float_neq_(Actual, Expected, Epsilon, ...) \ - cr_assert_impl(NORMAL, (Expected) - (Actual) > (Epsilon) \ - || (Actual) - (Expected) > (Epsilon), \ - __VA_ARGS__) +# define cr_assert_arr_neq_cmp(...) CRITERION_GNUC_WARN_(cr_assert_arr_neq_cmp) CR_NOOP +# define cr_expect_arr_neq_cmp(...) CRITERION_GNUC_WARN_(cr_expect_arr_neq_cmp) CR_NOOP -// String asserts +# define cr_assert_arr_lt_cmp(...) CRITERION_GNUC_WARN_(cr_assert_arr_lt_cmp) CR_NOOP +# define cr_expect_arr_lt_cmp(...) CRITERION_GNUC_WARN_(cr_expect_arr_lt_cmp) CR_NOOP -# define cr_assert_strings_(Op, Actual, Expected, ...) \ - cr_assert_impl(FATAL, strcmp((Actual), (Expected)) Op 0, __VA_ARGS__) -# define cr_expect_strings_(Op, Actual, Expected, ...) \ - cr_assert_impl(NORMAL, strcmp((Actual), (Expected)) Op 0, __VA_ARGS__) - -# define cr_assert_strings_eq(...) \ - cr_assert_strings_(==, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_strings_eq(...) \ - cr_expect_strings_(==, __VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_strings_neq(...) \ - cr_assert_strings_(!=, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_strings_neq(...) \ - cr_expect_strings_(!=, __VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_strings_gt(...) cr_assert_strings_(>, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_strings_gt(...) cr_expect_strings_(>, __VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_strings_lt(...) cr_assert_strings_(<, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_strings_lt(...) cr_expect_strings_(<, __VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_strings_leq(...) cr_assert_strings_(<=, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_strings_leq(...) cr_expect_strings_(<=, __VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_strings_geq(...) cr_assert_strings_(>=, __VA_ARGS__, .sentinel_ = 0) -# define cr_expect_strings_geq(...) cr_expect_strings_(>=, __VA_ARGS__, .sentinel_ = 0) - -// Array asserts - -# define cr_assert_arrays_eq(...) \ - cr_assert_arrays_eq_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_arrays_eq(...) \ - cr_expect_arrays_eq_(__VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_arrays_neq(...) \ - cr_assert_arrays_neq_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_arrays_neq(...) \ - cr_expect_arrays_neq_(__VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_arrays_eq_(A, B, Size, ...) \ - cr_assert_impl(FATAL, !memcmp((A), (B), (Size)), \ - .default_msg = "Arrays are not equal.", \ - __VA_ARGS__) -# define cr_expect_arrays_eq_(A, B, Size, ...) \ - cr_assert_impl(NORMAL, !memcmp((A), (B), (Size)), \ - .default_msg = "Arrays are not equal.", \ - __VA_ARGS__) - -# define cr_assert_arrays_neq_(A, B, Size, ...) \ - cr_assert_impl(FATAL, memcmp((A), (B), (Size)), \ - .default_msg = "Arrays are equal", \ - __VA_ARGS__) -# define cr_expect_arrays_neq_(A, B, Size, ...) \ - cr_assert_impl(NORMAL, memcmp((A), (B), (Size)), \ - .default_msg = "Arrays are equal", \ - __VA_ARGS__) +# define cr_assert_arr_leq_cmp(...) CRITERION_GNUC_WARN_(cr_assert_arr_leq_cmp) CR_NOOP +# define cr_expect_arr_leq_cmp(...) CRITERION_GNUC_WARN_(cr_expect_arr_leq_cmp) CR_NOOP -# ifdef __GNUC__ -# define CRIT_ARR_COMPARE_(A, B, Size, Cmp, Result) \ - __typeof__(&(A)[0]) first = (A); \ - __typeof__(&(B)[0]) second = (B); \ - int equals = 1; \ - size_t i, size; \ - for (i = 0, size = (Size); equals && i < size; ++i) \ - equals = equals && !Cmp(first + i, second + i) - -# define cr_assert_arrays_eq_cmp_(A, B, Size, Cmp, ...) \ - do { \ - CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \ - cr_assert_impl(FATAL, equals, \ - .default_msg = "Arrays are not equal", \ - __VA_ARGS__); \ - } while (0) +# define cr_assert_arr_gt_cmp(...) CRITERION_GNUC_WARN_(cr_assert_arr_gt_cmp) CR_NOOP +# define cr_expect_arr_gt_cmp(...) CRITERION_GNUC_WARN_(cr_expect_arr_gt_cmp) CR_NOOP -# define cr_expect_arrays_eq_cmp_(A, B, Size, Cmp, ...) \ - do { \ - CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \ - cr_assert_impl(NORMAL, equals, \ - .default_msg = "Arrays are not equal", \ - __VA_ARGS__); \ - } while (0) +# define cr_assert_arr_geq_cmp(...) CRITERION_GNUC_WARN_(cr_assert_arr_geq_cmp) CR_NOOP +# define cr_expect_arr_geq_cmp(...) CRITERION_GNUC_WARN_(cr_expect_arr_geq_cmp) CR_NOOP -# define cr_assert_arrays_eq_cmp(...) \ - cr_assert_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_arrays_eq_cmp(...) \ - cr_expect_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0) - -# define cr_assert_arrays_neq_cmp_(A, B, Size, Cmp, ...) \ - do { \ - CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \ - cr_assert_impl(FATAL, !equals, \ - .default_msg = "Arrays not equal", \ - __VA_ARGS__); \ - } while (0) +# endif -# define cr_expect_arrays_neq_cmp_(A, B, Size, Cmp, ...) \ - do { \ - CRIT_ARR_COMPARE_(A, B, Size, Cmp, equals); \ - cr_assert_impl(NORMAL, equals, \ - .default_msg = "Arrays not equal", \ - __VA_ARGS__); \ - } while (0) +# ifdef __cplusplus + +# define cr_assert_throw_abort_(Fail, Msg, MsgArgs, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + 0, \ + dummy, \ + Msg, \ + MsgArgs, \ + CR_VA_TAIL(__VA_ARGS__) \ + )) + +# define cr_assert_throw_(Fail, Statement, Exception, ...) \ + try { \ + Statement; \ + } catch (Exception const &) { \ + } catch (...) { \ + CR_EXPAND(cr_assert_throw_abort_( \ + Fail, \ + CRITERION_ASSERT_MSG_NO_THROW, \ + (CR_STR(Statement), CR_STR(Exception)), \ + __VA_ARGS__)); \ + } + +# define cr_assert_throw_va_(...) \ + CR_EXPAND(cr_assert_throw_( \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_HEAD(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))), \ + dummy, \ + CR_VA_TAIL(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))) \ + )) + +# define cr_assert_throw(...) CR_EXPAND(cr_assert_throw_va_(CR_FAIL_ABORT_, __VA_ARGS__)) +# define cr_expect_throw(...) CR_EXPAND(cr_assert_throw_va_(CR_FAIL_CONTINUES_, __VA_ARGS__)) + +# define cr_assert_no_throw_(Fail, Statement, Exception, ...) \ + try { \ + Statement; \ + } catch (Exception const &) { \ + CR_EXPAND(cr_assert_throw_abort_( \ + Fail, \ + CRITERION_ASSERT_MSG_THROW, \ + (CR_STR(Statement), CR_STR(Exception)), \ + __VA_ARGS__)); \ + } + +# define cr_assert_no_throw_va_(...) \ + CR_EXPAND(cr_assert_no_throw_( \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_HEAD(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))), \ + dummy, \ + CR_VA_TAIL(CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))) \ + )) + +# define cr_assert_no_throw(...) CR_EXPAND(cr_assert_no_throw_va_(CR_FAIL_ABORT_, __VA_ARGS__)) +# define cr_expect_no_throw(...) CR_EXPAND(cr_assert_no_throw_va_(CR_FAIL_CONTINUES_, __VA_ARGS__)) + +# define cr_assert_any_throw_(Fail, Statement, ...) \ + try { \ + Statement; \ + CR_EXPAND(cr_assert_throw_abort_( \ + Fail, \ + CRITERION_ASSERT_MSG_ANY_THROW, \ + (CR_STR(Statement)), \ + __VA_ARGS__)); \ + } catch (...) {} + +# define cr_assert_any_throw_va_(...) \ + CR_EXPAND(cr_assert_any_throw_( \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + dummy, \ + CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)) \ + )) + +# define cr_assert_any_throw(...) CR_EXPAND(cr_assert_any_throw_va_(CR_FAIL_ABORT_, __VA_ARGS__)) +# define cr_expect_any_throw(...) CR_EXPAND(cr_assert_any_throw_va_(CR_FAIL_CONTINUES_, __VA_ARGS__)) + +# define cr_assert_none_throw_(Fail, Statement, ...) \ + try { \ + Statement; \ + } catch (...) { \ + CR_EXPAND(cr_assert_throw_abort_( \ + Fail, \ + CRITERION_ASSERT_MSG_NONE_THROW, \ + (CR_STR(Statement)), \ + __VA_ARGS__)); \ + } + +# define cr_assert_none_throw_va_(...) \ + CR_EXPAND(cr_assert_none_throw_( \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + dummy, \ + CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)) \ + )) + +# define cr_assert_none_throw(...) CR_EXPAND(cr_assert_none_throw_va_(CR_FAIL_ABORT_, __VA_ARGS__)) +# define cr_expect_none_throw(...) CR_EXPAND(cr_assert_none_throw_va_(CR_FAIL_CONTINUES_, __VA_ARGS__)) -# define cr_assert_arrays_neq_cmp(...) \ - cr_assert_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0) -# define cr_expect_arrays_neq_cmp(...) \ - cr_expect_arrays_eq_cmp_(__VA_ARGS__, .sentinel_ = 0) -# endif /* !__GNUC__ */ +# endif // The section below is here for backward compatibility purposes. -// It shall be removed in the text major version of Criterion +// It shall be removed in the next major version of Criterion # ifndef CRITERION_NO_COMPAT -# define CRITERION_ASSERT_DEPRECATED_(Name) CRITERION_ASSERT_DEPRECATED__( \ - message \ - "The `" #Name "` macro is deprecated, " \ - "please use `cr_" #Name "` instead." \ +# define CRITERION_ASSERT_DEPRECATED_(Name) CRITERION_ASSERT_DEPRECATED__( \ + message \ + ("The `" #Name "` macro is deprecated, " \ + "please use `cr_" #Name "` instead.") \ ) +# define CRITERION_ASSERT_DEPRECATED_B(Name, Newname) \ + CRITERION_ASSERT_DEPRECATED__( \ + message \ + ("The `" #Name "` macro is deprecated, " \ + "please use `" #Newname "` instead.") \ + ) + +# ifdef _MSC_VER +# define CRITERION_ASSERT_DEPRECATED__(Msg) \ + __pragma(Msg) +# else # define CRITERION_ASSERT_DEPRECATED__(Msg) \ _Pragma(#Msg) +# endif # ifndef assert # define assert(...) CRITERION_ASSERT_DEPRECATED_(assert) cr_assert(__VA_ARGS__) @@ -292,6 +627,22 @@ struct criterion_assert_args { # endif /* !_ASSERT_H */ # endif /* !assert */ +// scheduled for removal after 2.0 +# define cr_abort_test(Message) CRITERION_ASSERT_DEPRECATED_B(cr_abort_test, cr_assert_fail) cr_assert_fail(Message) +# define cr_assert_strings_eq(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_strings_eq, cr_assert_str_eq) cr_assert_str_eq(__VA_ARGS__) +# define cr_assert_strings_neq(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_strings_neq, cr_assert_str_neq) cr_assert_str_neq(__VA_ARGS__) +# define cr_assert_strings_lt(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_strings_lt, cr_assert_str_lt) cr_assert_str_lt(__VA_ARGS__) +# define cr_assert_strings_leq(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_strings_leq, cr_assert_str_leq) cr_assert_str_leq(__VA_ARGS__) +# define cr_assert_strings_gt(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_strings_gt, cr_assert_str_gt) cr_assert_str_gt(__VA_ARGS__) +# define cr_assert_strings_geq(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_strings_geq, cr_assert_str_geq) cr_assert_str_geq(__VA_ARGS__) + +# define cr_assert_arrays_eq(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_arrays_eq, cr_assert_arr_eq) cr_assert_arr_eq(__VA_ARGS__) +# define cr_assert_arrays_neq(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_arrays_neq, cr_assert_arr_neq) cr_assert_arr_neq(__VA_ARGS__) + +# define cr_assert_arrays_eq_cmp(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_arrays_eq_cmp, cr_assert_arr_eq_cmp) cr_assert_arr_eq_cmp(__VA_ARGS__) +# define cr_assert_arrays_neq_cmp(...) CRITERION_ASSERT_DEPRECATED_B(cr_assert_arrays_neq_cmp, cr_assert_arr_neq_cmp) cr_assert_arr_neq_cmp(__VA_ARGS__) + +// scheduled for removal at 2.0 # define abort_test(Message) CRITERION_ASSERT_DEPRECATED_(abort_test) cr_abort_test(Message) # define expect(...) CRITERION_ASSERT_DEPRECATED_(expect) cr_expect(__VA_ARGS__) # define assert_not(...) CRITERION_ASSERT_DEPRECATED_(assert_not) cr_assert_not(__VA_ARGS__) diff --git a/include/criterion/common.h b/include/criterion/common.h index 3a8d3a84..ae962eb7 100644 --- a/include/criterion/common.h +++ b/include/criterion/common.h @@ -24,18 +24,58 @@ #ifndef CRITERION_COMMON_H_ # define CRITERION_COMMON_H_ +# if defined(_MSC_VER) +# if _MSC_VER < 1900 +# error \ + Your version of MSVC++ is too old, please compile your tests using \ + a c99 compiler, like MinGW or MSVC 14.0+ (Included in visual studio \ + 2015) +# endif +# endif + +# ifndef CR_IS_MSVC +# ifdef _MSC_VER +# define CR_IS_MSVC _MSC_VER +# else +# define CR_IS_MSVC 0 +# endif +# endif + +# ifdef __cplusplus +# define CR_ATTRIBUTE(Arg) [[gnu::Arg]] +# define CR_BEGIN_C_API extern "C" { +# define CR_END_C_API } +# else +# define CR_ATTRIBUTE(Arg) __attribute__((Arg)) +# define CR_BEGIN_C_API +# define CR_END_C_API +# endif + # ifdef __APPLE__ # define SECTION_START_PREFIX __first # define SECTION_END_PREFIX __last # define SECTION_START_SUFFIX(Name) __asm("section$start$__DATA$" Name) # define SECTION_END_SUFFIX(Name) __asm("section$end$__DATA$" Name) -# define SECTION_(Name) __attribute__((section("__DATA," Name))) +# define SECTION_(Name) CR_ATTRIBUTE(section("__DATA," Name)) +# define SECTION_SUFFIX_ +# elif CR_IS_MSVC +# define SECTION_START_PREFIX __start +# define SECTION_END_PREFIX __stop +# define SECTION_START_SUFFIX(Name) +# define SECTION_END_SUFFIX(Name) +# define SECTION_(Name) \ + __pragma(data_seg(push)) \ + __pragma(section(Name, read)) \ + __declspec(allocate(Name)) +# define SECTION_SUFFIX_ \ + __pragma(data_seg(pop)) # else # define SECTION_START_PREFIX __start # define SECTION_END_PREFIX __stop # define SECTION_START_SUFFIX(Name) # define SECTION_END_SUFFIX(Name) -# define SECTION_(Name) __attribute__((section(Name))) +# define SECTION_(Name) CR_ATTRIBUTE(section(Name)) +# define SECTION_SUFFIX_ # endif # define MAKE_IDENTIFIER_(Prefix, Id) MAKE_IDENTIFIER__(Prefix, Id) @@ -56,8 +96,19 @@ Type *const SECTION_START(Name) = &SECTION_START_(Name); \ Type *const SECTION_END(Name) = &SECTION_END_(Name) -# define UNUSED __attribute__((unused)) -# define NORETURN __attribute__((noreturn)) +# ifdef __GNUC__ +# define UNUSED CR_ATTRIBUTE(unused) +# define NORETURN CR_ATTRIBUTE(noreturn) +# define CR_INLINE CR_ATTRIBUTE(always_inline) inline +# elif CR_IS_MSVC +# define UNUSED +# define NORETURN __declspec(noreturn) +# define CR_INLINE __forceinline +# else +# define UNUSED +# define NORETURN +# define CR_INLINE inline +# endif # ifdef _WIN32 # define SIZE_T_FORMAT "%Iu" @@ -66,9 +117,34 @@ # endif # ifdef __GNUC__ -# define FORMAT(Archetype, Index, Ftc) __attribute__((format(Archetype, Index, Ftc))) +# define FORMAT(Archetype, Index, Ftc) CR_ATTRIBUTE(format(Archetype, Index, Ftc)) # else # define FORMAT(Archetype, Index, Ftc) # endif +# if defined _WIN32 || defined __CYGWIN__ +# ifdef CRITERION_BUILDING_DLL +# ifdef __GNUC__ +# define CR_API CR_ATTRIBUTE(dllexport) +# else +# define CR_API __declspec(dllexport) +# endif +# else +# ifdef __GNUC__ +# define CR_API CR_ATTRIBUTE(dllimport) +# else +# define CR_API __declspec(dllimport) +# endif +# endif +# define CR_LOCAL +# else +# if __GNUC__ >= 4 +# define CR_API CR_ATTRIBUTE(visibility("default")) +# define CR_LOCAL CR_ATTRIBUTE(visibility("hidden")) +# else +# define CR_API +# define CR_LOCAL +# endif +# endif + #endif /* !CRITERION_COMMON_H_ */ diff --git a/include/criterion/criterion.h b/include/criterion/criterion.h index c26ad266..82dab63d 100644 --- a/include/criterion/criterion.h +++ b/include/criterion/criterion.h @@ -24,49 +24,67 @@ #ifndef CRITERION_H_ # define CRITERION_H_ +# include "designated-initializer-compat.h" # include "common.h" -# include "assert.h" # include "types.h" +# include "assert.h" # define IDENTIFIER_(Category, Name, Suffix) \ Category ## _ ## Name ## _ ## Suffix -# define TEST_PROTOTYPE_(Category, Name) \ + +# ifdef __cplusplus +# define TEST_PROTOTYPE_(Category, Name) \ + extern "C" void IDENTIFIER_(Category, Name, impl)(void) +# else +# define TEST_PROTOTYPE_(Category, Name) \ void IDENTIFIER_(Category, Name, impl)(void) +# endif # define SUITE_IDENTIFIER_(Name, Suffix) \ suite_ ## Name ## _ ## Suffix -# define Test(...) Test_(__VA_ARGS__, .sentinel_ = 0) +# define Test(...) CR_EXPAND(Test_(__VA_ARGS__, .sentinel_ = 0)) # define Test_(Category, Name, ...) \ TEST_PROTOTYPE_(Category, Name); \ - struct criterion_test_extra_data IDENTIFIER_(Category, Name, extra) = { \ - .identifier_ = #Category "/" #Name, \ - .file_ = __FILE__, \ - .line_ = __LINE__, \ - __VA_ARGS__ \ - }; \ + struct criterion_test_extra_data IDENTIFIER_(Category, Name, extra) = \ + CR_EXPAND(CRITERION_MAKE_STRUCT(struct criterion_test_extra_data, \ + .identifier_ = #Category "/" #Name, \ + .file_ = __FILE__, \ + .line_ = __LINE__, \ + __VA_ARGS__ \ + )); \ SECTION_("cr_tst") \ - const struct criterion_test IDENTIFIER_(Category, Name, meta) = { \ - .name = #Name, \ - .category = #Category, \ - .test = IDENTIFIER_(Category, Name, impl), \ - .data = &IDENTIFIER_(Category, Name, extra) \ - }; \ + struct criterion_test IDENTIFIER_(Category, Name, meta) = { \ + #Name, \ + #Category, \ + IDENTIFIER_(Category, Name, impl), \ + &IDENTIFIER_(Category, Name, extra) \ + } SECTION_SUFFIX_; \ TEST_PROTOTYPE_(Category, Name) -# define TestSuite(...) TestSuite_(__VA_ARGS__, .sentinel_ = 0) +# define TestSuite(...) CR_EXPAND(TestSuite_(__VA_ARGS__, .sentinel_ = 0)) # define TestSuite_(Name, ...) \ - struct criterion_test_extra_data SUITE_IDENTIFIER_(Name, extra) = { \ - .file_ = __FILE__, \ - .line_ = 0, \ - __VA_ARGS__ \ - }; \ + struct criterion_test_extra_data SUITE_IDENTIFIER_(Name, extra) = \ + CR_EXPAND(CRITERION_MAKE_STRUCT(struct criterion_test_extra_data, \ + .file_ = __FILE__, \ + .line_ = 0, \ + __VA_ARGS__ \ + )); \ SECTION_("cr_sts") \ - const struct criterion_suite SUITE_IDENTIFIER_(Name, meta) = { \ - .name = #Name, \ - .data = &SUITE_IDENTIFIER_(Name, extra), \ - } + struct criterion_suite SUITE_IDENTIFIER_(Name, meta) = { \ + #Name, \ + &SUITE_IDENTIFIER_(Name, extra), \ + } SECTION_SUFFIX_ + +CR_BEGIN_C_API + +CR_API struct criterion_test_set *criterion_initialize(void); +CR_API void criterion_finalize(struct criterion_test_set *tests); +CR_API int criterion_run_all_tests(struct criterion_test_set *tests); +CR_API int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg); +CR_API void criterion_register_test(struct criterion_test_set *tests, + struct criterion_test *test); -int criterion_run_all_tests(void); +CR_END_C_API #endif /* !CRITERION_H_ */ diff --git a/include/criterion/designated-initializer-compat.h b/include/criterion/designated-initializer-compat.h new file mode 100644 index 00000000..3b2a7ba2 --- /dev/null +++ b/include/criterion/designated-initializer-compat.h @@ -0,0 +1,125 @@ +/* + * 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_DESIGNATED_INITIALIZER_COMPAT_HH_ +# define CRITERION_DESIGNATED_INITIALIZER_COMPAT_HH_ + +# include "common.h" + +# define CRITERION_ARG_LENGTH(...) CR_EXPAND(CRITERION_ARG_LENGTH_(__VA_ARGS__,\ + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45,\ + 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26,\ + 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6,\ + 5, 4, 3, 2, 1, 0)) +# define CRITERION_ARG_LENGTH_(_63, _62, _61, _60, _59, _58, _57, _56, _55, _54, _53, \ + _52, _51, _50, _49, _48, _47, _46, _45, _44, _43, _42, _41, _40, _39, _38, \ + _37, _36, _35, _34, _33, _32, _31, _30, _29, _28, _27, _26, _25, _24, _23, \ + _22, _21, _20, _19, _18, _17, _16, _15, _14, _13, _12, _11, _10, _9, _8, \ + _7, _6, _5, _4, _3, _2, _1, count, ...) count + +# define CRITERION_APPLY_1(Macro, ...) +# define CRITERION_APPLY_2(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_1(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_3(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_2(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_4(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_3(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_5(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_4(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_6(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_5(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_7(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_6(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_8(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_7(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_9(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_8(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_10(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_9(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_11(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_10(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_12(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_11(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_13(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_12(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_14(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_13(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_15(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_14(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_16(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_15(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_17(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_16(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_18(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_17(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_19(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_18(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_20(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_19(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_21(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_20(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_22(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_21(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_23(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_22(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_24(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_23(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_25(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_24(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_26(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_25(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_27(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_26(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_28(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_27(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_29(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_28(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_30(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_29(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_31(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_30(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_32(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_31(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_33(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_32(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_34(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_33(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_35(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_34(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_36(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_35(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_37(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_36(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_38(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_37(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_39(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_38(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_40(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_39(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_41(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_40(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_42(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_41(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_43(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_42(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_44(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_43(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_45(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_44(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_46(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_45(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_47(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_46(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_48(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_47(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_49(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_48(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_50(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_49(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_51(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_50(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_52(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_51(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_53(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_52(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_54(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_53(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_55(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_54(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_56(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_55(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_57(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_56(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_58(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_57(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_59(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_58(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_60(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_59(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_61(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_60(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_62(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_61(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_63(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_62(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_64(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_63(Macro, Prefix, __VA_ARGS__)) +# define CRITERION_APPLY_65(Macro, Prefix, Head, ...) Macro(Prefix, Head) CR_EXPAND(CRITERION_APPLY_64(Macro, Prefix, __VA_ARGS__)) + +# define CRITERION_APPLY__(Macro, Prefix, n, ...) CR_EXPAND(CRITERION_APPLY_##n(Macro, Prefix, __VA_ARGS__,)) +# define CRITERION_APPLY_(Macro, n, Prefix, ...) CR_EXPAND(CRITERION_APPLY__(Macro, Prefix, n, __VA_ARGS__)) +# define CRITERION_APPLY(Macro, ...) CR_EXPAND(CRITERION_APPLY_(Macro, CRITERION_ARG_LENGTH(__VA_ARGS__), __VA_ARGS__)) + +# define CRITERION_ADD_PREFIX_ONCE(Prefix, Field) Prefix Field; +# define CRITERION_ADD_PREFIX(...) \ + CR_EXPAND(CRITERION_APPLY(CRITERION_ADD_PREFIX_ONCE, __VA_ARGS__)) + +# ifdef __cplusplus +# define CRITERION_MAKE_STRUCT(Type, ...) [&]() { \ + Type t; \ + std::memset(&t, 0, sizeof (t)); \ + CR_EXPAND(CRITERION_ADD_PREFIX(t, __VA_ARGS__)) \ + return t; \ + }() +# else +# define CRITERION_MAKE_STRUCT(Type, ...) { __VA_ARGS__ } +# endif + +#endif /* !CRITERION_DESIGNATED_INITIALIZER_COMPAT_HH_ */ diff --git a/include/criterion/event.h b/include/criterion/event.h index 99c85c86..51389018 100644 --- a/include/criterion/event.h +++ b/include/criterion/event.h @@ -24,11 +24,17 @@ #ifndef CRITERION_EVENT_H_ # define CRITERION_EVENT_H_ -# include -# include +# ifdef __cplusplus +# include +# else +# include +# endif +# include "common.h" -extern FILE *g_event_pipe; +CR_BEGIN_C_API -void send_event(int kind, void *data, size_t size); +CR_API void send_event(int kind, void *data, size_t size); + +CR_END_C_API #endif /* !CRITERION_EVENT_H_ */ diff --git a/include/criterion/hooks.h b/include/criterion/hooks.h index c5974677..62ab4834 100644 --- a/include/criterion/hooks.h +++ b/include/criterion/hooks.h @@ -25,6 +25,7 @@ # define CRITERION_HOOKS_H_ # include "common.h" +# include "types.h" typedef enum { PRE_ALL, @@ -32,6 +33,7 @@ typedef enum { PRE_INIT, PRE_TEST, ASSERT, + THEORY_FAIL, TEST_CRASH, POST_TEST, POST_FINI, @@ -45,8 +47,13 @@ typedef void (*f_report_hook)(); # define HOOK_IDENTIFIER__(Line, Suffix) HOOK_IDENTIFIER___(Line, Suffix) # define HOOK_IDENTIFIER___(Line, Suffix) hook_l ## Line ## _ ## Suffix -# define HOOK_PROTOTYPE_ \ +# ifdef __cplusplus +# define HOOK_PROTOTYPE_ \ + extern "C" void HOOK_IDENTIFIER_(impl) +# else +# define HOOK_PROTOTYPE_ \ void HOOK_IDENTIFIER_(impl) +# endif // Section abbreviations # define HOOK_SECTION_PRE_ALL cr_pra @@ -54,6 +61,7 @@ typedef void (*f_report_hook)(); # define HOOK_SECTION_PRE_INIT cr_pri # define HOOK_SECTION_PRE_TEST cr_prt # define HOOK_SECTION_ASSERT cr_ast +# define HOOK_SECTION_THEORY_FAIL cr_thf # define HOOK_SECTION_TEST_CRASH cr_tsc # define HOOK_SECTION_POST_TEST cr_pot # define HOOK_SECTION_POST_FINI cr_pof @@ -66,10 +74,26 @@ typedef void (*f_report_hook)(); # define HOOK_SECTION_STRINGIFY_(Sec) HOOK_SECTION_STRINGIFY__(Sec) # define HOOK_SECTION_STRINGIFY(Kind) HOOK_SECTION_STRINGIFY_(HOOK_SECTION(Kind)) +# define HOOK_PARAM_TYPE_PRE_ALL struct criterion_test_set * +# define HOOK_PARAM_TYPE_PRE_SUITE struct criterion_suite_set * +# define HOOK_PARAM_TYPE_PRE_INIT struct criterion_test * +# define HOOK_PARAM_TYPE_PRE_TEST struct criterion_test * +# define HOOK_PARAM_TYPE_ASSERT struct criterion_assert_stats * +# define HOOK_PARAM_TYPE_THEORY_FAIL struct criterion_theory_stats * +# define HOOK_PARAM_TYPE_TEST_CRASH struct criterion_test_stats * +# define HOOK_PARAM_TYPE_POST_TEST struct criterion_test_stats * +# define HOOK_PARAM_TYPE_POST_FINI struct criterion_test_stats * +# define HOOK_PARAM_TYPE_POST_SUITE struct criterion_suite_stats * +# define HOOK_PARAM_TYPE_POST_ALL struct criterion_global_stats * + +# define HOOK_PARAM_TYPE(Kind) HOOK_PARAM_TYPE_ ## Kind + # define ReportHook(Kind) \ - HOOK_PROTOTYPE_(); \ + HOOK_PROTOTYPE_(HOOK_PARAM_TYPE(Kind)); \ SECTION_(HOOK_SECTION_STRINGIFY(Kind)) \ - const f_report_hook HOOK_IDENTIFIER_(func) = HOOK_IDENTIFIER_(impl); \ + f_report_hook HOOK_IDENTIFIER_(func) = \ + (f_report_hook) HOOK_IDENTIFIER_(impl) \ + SECTION_SUFFIX_; \ HOOK_PROTOTYPE_ #endif /* !CRITERION_HOOKS_H_ */ diff --git a/include/criterion/logging.h b/include/criterion/logging.h index 616dac9e..17baf4b5 100644 --- a/include/criterion/logging.h +++ b/include/criterion/logging.h @@ -24,8 +24,13 @@ #ifndef CRITERION_LOGGING_H_ # define CRITERION_LOGGING_H_ -# include -# include +# ifdef __cplusplus +# include +using std::va_list; +# else +# include +# include +# endif # include "common.h" # include "ordered-set.h" # include "stats.h" @@ -52,12 +57,12 @@ struct criterion_prefix_data { # 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 CRIT_FG_BOLD "\33[0;1m" +# define CRIT_FG_RED "\33[0;31m" +# define CRIT_FG_GREEN "\33[0;32m" +# define CRIT_FG_GOLD "\33[0;33m" +# define CRIT_FG_BLUE "\33[0;34m" +# define CRIT_RESET "\33[0m" # define FG_BOLD CRIT_COLOR_NORMALIZE(CRIT_FG_BOLD) # define FG_RED CRIT_COLOR_NORMALIZE(CRIT_FG_RED) @@ -67,6 +72,8 @@ struct criterion_prefix_data { # define RESET CRIT_COLOR_NORMALIZE(CRIT_RESET) # endif +CR_BEGIN_C_API + extern const struct criterion_prefix_data g_criterion_logging_prefixes[]; # define CRITERION_PREFIX_DASHES (&g_criterion_logging_prefixes[CRITERION_LOGGING_PREFIX_DASHES]) @@ -76,13 +83,13 @@ extern const struct criterion_prefix_data g_criterion_logging_prefixes[]; # 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); +CR_API 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, ...); +CR_API 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, ...); +CR_API 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__) @@ -91,22 +98,27 @@ void criterion_log(enum criterion_logging_level level, const char *msg, ...); # 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_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); + 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_theory_fail )(struct criterion_theory_stats *stats); + void (*log_test_timeout )(struct criterion_test_stats *stats); + void (*log_test_crash )(struct criterion_test_stats *stats); + void (*log_other_crash )(struct criterion_test_stats *stats); + void (*log_abnormal_exit)(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; extern struct criterion_output_provider tap_logging; +CR_END_C_API + #define NORMAL_LOGGING (&normal_logging) #define TAP_LOGGING (&tap_logging) diff --git a/include/criterion/options.h b/include/criterion/options.h index 31b90d2c..0bdcdeef 100644 --- a/include/criterion/options.h +++ b/include/criterion/options.h @@ -38,6 +38,10 @@ struct criterion_options { bool short_filename; }; +CR_BEGIN_C_API + extern struct criterion_options criterion_options; +CR_END_C_API + #endif /*!CRITERION_OPTIONS_H_ */ diff --git a/include/criterion/ordered-set.h b/include/criterion/ordered-set.h index 4b71081b..24044f81 100644 --- a/include/criterion/ordered-set.h +++ b/include/criterion/ordered-set.h @@ -40,23 +40,17 @@ struct criterion_ordered_set_node { char data[0]; }; -struct criterion_suite_set { - struct criterion_suite suite; - struct criterion_ordered_set *tests; -}; - -struct criterion_test_set { - struct criterion_ordered_set *suites; - size_t tests; -}; +CR_BEGIN_C_API -struct criterion_ordered_set *new_ordered_set(f_criterion_cmp cmp, +CR_API struct criterion_ordered_set *new_ordered_set(f_criterion_cmp cmp, void (*dtor)(void *, void *)); -void *insert_ordered_set(struct criterion_ordered_set *l, +CR_API void *insert_ordered_set(struct criterion_ordered_set *l, void *ptr, size_t size); +CR_END_C_API + # define FOREACH_SET(Elt, Set) \ for (struct criterion_ordered_set_node *n = Set->first; n; n = n->next) \ for (int cond = 1; cond;) \ diff --git a/include/criterion/preprocess.h b/include/criterion/preprocess.h new file mode 100644 index 00000000..aee370a3 --- /dev/null +++ b/include/criterion/preprocess.h @@ -0,0 +1,73 @@ +/* + * 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_PREPROCESS_H_ +# define CRITERION_PREPROCESS_H_ + +# define CR_NOOP do {} while(0) + +# ifdef __cplusplus +# define CR_NOTHROW throw() +# else +# define CR_NOTHROW +# endif + +# define CR_EXPAND(x) x +# define CR_IDENTITY(...) __VA_ARGS__ + +# define CR_STR(x) CR_EXPAND(CR_STR_(x)) +# define CR_STR_(x) #x + +# define CR_VA_TAIL(...) CR_EXPAND(CR_VA_TAIL_HELPER(CR_VA_TAIL_SELECT(__VA_ARGS__), __VA_ARGS__)) + +# define CR_VA_TAIL_HELPER(N, ...) CR_EXPAND(CR_VA_TAIL_HELPER_(N, __VA_ARGS__)) +# define CR_VA_TAIL_HELPER_(N, ...) CR_EXPAND(CR_VA_TAIL_HELPER_##N(__VA_ARGS__)) +# define CR_VA_TAIL_HELPER_1(Head) +# define CR_VA_TAIL_HELPER_2(Head, ...) __VA_ARGS__ + +# define CR_VA_HEAD(...) CR_EXPAND(CR_VA_HEAD_HELPER(CR_VA_TAIL_SELECT(__VA_ARGS__), __VA_ARGS__)) + +# define CR_VA_HEAD_HELPER(N, ...) CR_EXPAND(CR_VA_HEAD_HELPER_(N, __VA_ARGS__)) +# define CR_VA_HEAD_HELPER_(N, ...) CR_EXPAND(CR_VA_HEAD_HELPER_##N(__VA_ARGS__)) +# define CR_VA_HEAD_HELPER_1(Head) Head +# define CR_VA_HEAD_HELPER_2(Head, ...) Head + +# define CR_VA_TAIL_SELECT(...) CR_EXPAND(CR_VA_TAIL_SELECT64(__VA_ARGS__, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 1, _)) + +# define CR_VA_TAIL_SELECT64( \ + _01, _02, _03, _04, _05, _06, _07, _08, _09, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \ + _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \ + _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \ + _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \ + _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \ + _61, _62, _63, X, ...) X + +#endif /* !CRITERION_PREPROCESS_H_ */ diff --git a/include/criterion/redirect.h b/include/criterion/redirect.h new file mode 100644 index 00000000..7adaf9b3 --- /dev/null +++ b/include/criterion/redirect.h @@ -0,0 +1,235 @@ +/* + * 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_REDIRECT_H_ +# define CRITERION_REDIRECT_H_ + +# include "common.h" +# include "assert.h" + +# ifdef __cplusplus +# include +# include +# include + +# ifdef __GNUC__ +# include +# endif +# else +# include +# endif + +CR_BEGIN_C_API + +CR_API void cr_redirect_stdout(void); +CR_API void cr_redirect_stderr(void); +CR_API void cr_redirect_stdin(void); + +CR_API CR_STDN FILE* cr_get_redirected_stdout(void); +CR_API CR_STDN FILE* cr_get_redirected_stderr(void); +CR_API CR_STDN FILE* cr_get_redirected_stdin(void); + +CR_API int cr_file_match_str(CR_STDN FILE* f, const char *str); +CR_API int cr_file_match_file(CR_STDN FILE* f, CR_STDN FILE* ref); + +CR_END_C_API + +# define cr_assert_redir_op_(Fail, Fun, Op, File, Str, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + !(Fun((File), (Str)) Op 0), \ + dummy, \ + CRITERION_ASSERT_MSG_FILE_STR_MATCH, \ + (CR_STR(File), Str), \ + __VA_ARGS__ \ + )) + +# define cr_assert_redir_op_va_(Fail, Fun, Op, ...) \ + CR_EXPAND(cr_assert_redir_op_( \ + Fail, \ + Fun, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)) \ + )) + +# define cr_assert_redir_f_op_(Fail, Fun, Op, File, Ref, ...) \ + CR_EXPAND(cr_assert_impl( \ + Fail, \ + !(Fun((File), (Ref)) Op 0), \ + dummy, \ + CRITERION_ASSERT_MSG_FILE_MATCH, \ + (CR_STR(File), CR_STR(Ref)), \ + __VA_ARGS__ \ + )) + +# define cr_assert_redir_f_op_va_(Fail, Fun, Op, ...) \ + CR_EXPAND(cr_assert_redir_op_( \ + Fail, \ + Fun, \ + Op, \ + CR_VA_HEAD(__VA_ARGS__), \ + CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__)), \ + CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__)) \ + )) + +# define cr_assert_file_contents_eq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, ==, __VA_ARGS__)) +# define cr_expect_file_contents_eq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, ==, __VA_ARGS__)) + +# define cr_assert_file_contents_neq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, !=, __VA_ARGS__)) +# define cr_expect_file_contents_neq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, !=, __VA_ARGS__)) + +# define cr_assert_file_contents_eq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_ABORT_, cr_file_match_file, ==, __VA_ARGS__)) +# define cr_expect_file_contents_eq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_CONTINUES_, cr_file_match_file, ==, __VA_ARGS__)) + +# define cr_assert_file_contents_neq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_ABORT_, cr_file_match_file, !=, __VA_ARGS__)) +# define cr_expect_file_contents_neq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_CONTINUES_, cr_file_match_file, !=, __VA_ARGS__)) + +# define cr_assert_stdout_eq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, ==, cr_get_redirected_stdout(), __VA_ARGS__)) +# define cr_expect_stdout_eq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, ==, cr_get_redirected_stdout(), __VA_ARGS__)) + +# define cr_assert_stdout_neq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, !=, cr_get_redirected_stdout(), __VA_ARGS__)) +# define cr_expect_stdout_neq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, !=, cr_get_redirected_stdout(), __VA_ARGS__)) + +# define cr_assert_stderr_eq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, ==, cr_get_redirected_stderr(), __VA_ARGS__)) +# define cr_expect_stderr_eq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, ==, cr_get_redirected_stderr(), __VA_ARGS__)) + +# define cr_assert_stderr_neq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_ABORT_, cr_file_match_str, !=, cr_get_redirected_stderr(), __VA_ARGS__)) +# define cr_expect_stderr_neq_str(...) CR_EXPAND(cr_assert_redir_op_va_(CR_FAIL_CONTINUES_, cr_file_match_str, !=, cr_get_redirected_stderr(), __VA_ARGS__)) + +# define cr_assert_stdout_eq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_ABORT_, cr_file_match_file, ==, cr_get_redirected_stdout(), __VA_ARGS__)) +# define cr_expect_stdout_eq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_CONTINUES_, cr_file_match_file, ==, cr_get_redirected_stdout(), __VA_ARGS__)) + +# define cr_assert_stdout_neq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_ABORT_, cr_file_match_file, !=, cr_get_redirected_stdout(), __VA_ARGS__)) +# define cr_expect_stdout_neq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_CONTINUES_, cr_file_match_file, !=, cr_get_redirected_stdout(), __VA_ARGS__)) + +# define cr_assert_stderr_eq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_ABORT_, cr_file_match_file, ==, cr_get_redirected_stderr(), __VA_ARGS__)) +# define cr_expect_stderr_eq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_CONTINUES_, cr_file_match_file, ==, cr_get_redirected_stderr(), __VA_ARGS__)) + +# define cr_assert_stderr_neq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_ABORT_, cr_file_match_file, !=, cr_get_redirected_stderr(), __VA_ARGS__)) +# define cr_expect_stderr_neq(...) CR_EXPAND(cr_assert_redir_f_op_va_(CR_FAIL_CONTINUES_, cr_file_match_file, !=, cr_get_redirected_stderr(), __VA_ARGS__)) + +# ifdef __cplusplus +namespace criterion { + + template + class basic_ofstream : public std::basic_ofstream { + public: + basic_ofstream(FILE* f) +# ifdef __GNUC__ + : std::ofstream() + , fbuf(new ::__gnu_cxx::stdio_filebuf(f, std::ios::out)) +# else + : std::ofstream(f) +# endif + , file(f) + { +# ifdef __GNUC__ + std::ios::rdbuf(&*fbuf); +# endif + } + + void close(void) { + std::basic_ofstream::flush(); + std::basic_ofstream::close(); + std::fclose(file); + } + + private: +# ifdef __GNUC__ + std::unique_ptr<::__gnu_cxx::stdio_filebuf> fbuf; +# endif + std::FILE* file; + }; + + template + class basic_ifstream : public std::basic_ifstream { + public: + basic_ifstream(FILE* f) +# ifdef __GNUC__ + : std::ifstream() + , fbuf(new ::__gnu_cxx::stdio_filebuf(f, std::ios::in)) +# else + : std::ifstream(f) +# endif + , file(f) + { +# ifdef __GNUC__ + std::ios::rdbuf(&*fbuf); +# endif + } + + void close(void) { + std::basic_ifstream::flush(); + std::basic_ifstream::close(); + std::fclose(file); + } + + private: +# ifdef __GNUC__ + std::unique_ptr<::__gnu_cxx::stdio_filebuf> fbuf; +# endif + std::FILE* file; + }; + + template + struct get_redirected_out_stream_ { + static inline basic_ofstream& call(std::FILE* f) { + static std::unique_ptr> stream; + + if (!stream) + stream.reset(new basic_ofstream(f)); + return *stream; + } + + }; + + template + struct get_redirected_in_stream_ { + static inline basic_ifstream& call(std::FILE* f) { + static std::unique_ptr> stream; + if (!stream) + stream.reset(new basic_ifstream(f)); + return *stream; + } + }; + + using ofstream = basic_ofstream; + using ifstream = basic_ifstream; + + static inline ofstream& get_redirected_cin(void) { + return get_redirected_out_stream_::call(cr_get_redirected_stdin()); + } + + static inline ifstream& get_redirected_cout(void) { + return get_redirected_in_stream_::call(cr_get_redirected_stdout()); + } + + static inline ifstream& get_redirected_cerr(void) { + return get_redirected_in_stream_::call(cr_get_redirected_stderr()); + } +} +# endif + +#endif /* !CRITERION_REDIRECT_H_ */ diff --git a/include/criterion/stats.h b/include/criterion/stats.h index 8a1b4e7f..ec80c2ff 100644 --- a/include/criterion/stats.h +++ b/include/criterion/stats.h @@ -27,8 +27,6 @@ # include "types.h" struct criterion_assert_stats { - int kind; - const char *condition; const char *message; bool passed; unsigned line; @@ -44,13 +42,20 @@ struct criterion_test_stats { int passed_asserts; int failed_asserts; int signal; + int exit_code; float elapsed_time; + bool timed_out; unsigned progress; const char *file; struct criterion_test_stats *next; }; +struct criterion_theory_stats { + const char *formatted_args; + struct criterion_test_stats *stats; +}; + struct criterion_suite_stats { struct criterion_suite *suite; struct criterion_test_stats *tests; diff --git a/include/criterion/theories.h b/include/criterion/theories.h new file mode 100644 index 00000000..29055b53 --- /dev/null +++ b/include/criterion/theories.h @@ -0,0 +1,145 @@ +/* + * 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_THEORIES_H_ +# define CRITERION_THEORIES_H_ + +# ifdef __cplusplus +# include +using std::size_t; +# else +# include +# endif + +# include "criterion.h" + +# ifdef __cplusplus +template +constexpr size_t criterion_va_num__(const T &...) { + return sizeof...(T); +} +# endif + +CR_BEGIN_C_API + +struct criterion_theory_context; + +CR_API struct criterion_theory_context* cr_theory_init(void); +CR_API void cr_theory_push_arg(struct criterion_theory_context *ctx, bool is_float, size_t size, void *ptr); +CR_API void cr_theory_free(struct criterion_theory_context *ctx); +CR_API void cr_theory_abort(void); +CR_API int cr_theory_mark(void); + +CR_API void cr_theory_reset(struct criterion_theory_context *ctx); +CR_API void cr_theory_call(struct criterion_theory_context *ctx, void (*fnptr)(void)); + +# define TheoryDataPoints(Category, Name) \ + static struct criterion_datapoints IDENTIFIER_(Category, Name, dps)[] + +# define TheoryDataPoint(Category, Name) \ + (IDENTIFIER_(Category, Name, dps)) + +# ifdef __cplusplus +# define CR_TH_VA_NUM(Type, ...) criterion_va_num__(__VA_ARGS__) +# define CR_TH_TEMP_ARRAY(Type, ...) []() { static Type arr[] = { __VA_ARGS__ }; return &arr; }() +# else +# define CR_TH_VA_NUM(Type, ...) sizeof ((Type[]) { __VA_ARGS__ }) / sizeof (Type) +# define CR_TH_TEMP_ARRAY(Type, ...) &(Type[]) { __VA_ARGS__ } +# endif + +# define DataPoints(Type, ...) { \ + sizeof (Type), \ + CR_EXPAND(CR_TH_VA_NUM(Type, __VA_ARGS__)), \ + #Type, \ + CR_EXPAND(CR_TH_TEMP_ARRAY(Type, __VA_ARGS__)), \ + } + +struct criterion_datapoints { + size_t size; + size_t len; + const char *name; + void *arr; +}; + +# define CR_NB_DATAPOINTS(Var) \ + (sizeof (Var) / sizeof (struct criterion_datapoints)) + +# define cr_assume(Condition) \ + do { \ + if (!(Condition)) \ + cr_theory_abort(); \ + } while (0); + +# define cr_assume_not(Condition) cr_assume(!(Condition)) + +# define cr_assume_op_(Op, Actual, Expected) cr_assume((Actual) Op (Expected)) +# define cr_assume_eq(Actual, Expected) cr_assume_op_(==, Actual, Expected) +# define cr_assume_neq(Actual, Expected) cr_assume_op_(!=, Actual, Expected) +# define cr_assume_gt(Actual, Expected) cr_assume_op_(>, Actual, Expected) +# define cr_assume_geq(Actual, Expected) cr_assume_op_(>=, Actual, Expected) +# define cr_assume_lt(Actual, Expected) cr_assume_op_(<, Actual, Expected) +# define cr_assume_leq(Actual, Expected) cr_assume_op_(<=, Actual, Expected) + +# define cr_assume_null(Value) cr_assume_eq(Value, NULL) +# define cr_assume_not_null(Value) cr_assume_neq(Value, NULL) + +# define cr_assume_float_eq(Actual, Expected, Epsilon) \ + cr_assume((Expected) - (Actual) <= (Epsilon) \ + && (Actual) - (Expected) <= (Epsilon)) + +# define cr_assume_float_neq(Actual, Expected, Epsilon) \ + cr_assume((Expected) - (Actual) > (Epsilon) \ + || (Actual) - (Expected) > (Epsilon)) + +# define cr_assume_strings_op_(Op, Actual, Expected) \ + cr_assume(strcmp((Actual), (Expected)) Op 0) + +# define cr_assume_strings_eq(Actual, Expected) cr_assume_strings_op_(==, Actual, Expected) +# define cr_assume_strings_neq(Actual, Expected) cr_assume_strings_op_(!=, Actual, Expected) +# define cr_assume_strings_lt(Actual, Expected) cr_assume_strings_op_(<, Actual, Expected) +# define cr_assume_strings_leq(Actual, Expected) cr_assume_strings_op_(<=, Actual, Expected) +# define cr_assume_strings_gt(Actual, Expected) cr_assume_strings_op_(>, Actual, Expected) +# define cr_assume_strings_geq(Actual, Expected) cr_assume_strings_op_(>=, Actual, Expected) + +# define cr_assume_arrays_eq(Actual, Expected, Size) cr_assume(!memcmp((A), (B), (Size))) +# define cr_assume_arrays_neq(Actual, Expected, Size) cr_assume(memcmp((A), (B), (Size))) + +CR_API void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (*fnptr)(void)); + +# define CR_VAARG_ID(Suffix, Category, Name, ...) \ + IDENTIFIER_(Category, Name, Suffix) + +# define Theory(Args, ...) \ + void CR_EXPAND(CR_VAARG_ID(theory, __VA_ARGS__,))Args; \ + CR_EXPAND(Test_(__VA_ARGS__, .sentinel_ = 0)) { \ + cr_theory_main( \ + CR_EXPAND(CR_VAARG_ID(dps, __VA_ARGS__,)), \ + CR_NB_DATAPOINTS(CR_EXPAND(CR_VAARG_ID(dps, __VA_ARGS__,))), \ + (void(*)(void)) CR_EXPAND(CR_VAARG_ID(theory, __VA_ARGS__,)) \ + ); \ + } \ + void CR_EXPAND(CR_VAARG_ID(theory, __VA_ARGS__,))Args + +CR_END_C_API + +#endif /* !CRITERION_THEORIES_H_ */ diff --git a/include/criterion/types.h b/include/criterion/types.h index f5081746..e9e948fd 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -24,8 +24,13 @@ #ifndef CRITERION_TYPES_H_ # define CRITERION_TYPES_H_ -# include -# include +# ifdef __cplusplus +# include +using std::size_t; +# else +# include +# include +# endif # include "common.h" struct criterion_test_extra_data { @@ -36,8 +41,10 @@ struct criterion_test_extra_data { void (*init)(void); void (*fini)(void); int signal; + int exit_code; bool disabled; const char *description; + float timeout; void *data; }; @@ -53,6 +60,18 @@ struct criterion_suite { struct criterion_test_extra_data *data; }; +struct criterion_ordered_set; + +struct criterion_suite_set { + struct criterion_suite suite; + struct criterion_ordered_set *tests; +}; + +struct criterion_test_set { + struct criterion_ordered_set *suites; + size_t tests; +}; + typedef void (*f_worker_func)(struct criterion_test *, struct criterion_suite *); #endif /* !CRITERION_TYPES_H_ */ diff --git a/po/POTFILES.in b/po/POTFILES.in index d9d2b168..e93e6cdf 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,2 +1,3 @@ # List of source files which contain translatable strings. src/log/normal.c +src/i18n.c diff --git a/po/fr.po b/po/fr.po index af25ab87..5b3eb93a 100644 --- a/po/fr.po +++ b/po/fr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: criterion 1.0.0\n" "Report-Msgid-Bugs-To: franklinmathieu+criterion@gmail.com\n" -"POT-Creation-Date: 2015-08-05 11:37+0200\n" +"POT-Creation-Date: 2015-09-14 04:12+0200\n" "PO-Revision-Date: 2015-04-03 17:58+0200\n" "Last-Translator: \n" "Language-Team: French\n" @@ -18,54 +18,65 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: src/log/normal.c:51 +#: src/log/normal.c:50 #, c-format msgid "Criterion v%s\n" msgstr "Criterion v%s\n" -#: src/log/normal.c:52 +#: src/log/normal.c:51 #, c-format msgid " %s\n" msgstr " %s\n" -#: src/log/normal.c:55 src/log/normal.c:57 +#: src/log/normal.c:54 src/log/normal.c:56 #, c-format msgid "%1$s::%2$s\n" msgstr "%1$s::%2$s\n" -#: src/log/normal.c:56 +#: src/log/normal.c:55 #, fuzzy, c-format msgid "%1$s::%2$s: (%3$3.2fs)\n" msgstr "%1$s::%2$s: (%3$3.2fs)\n" -#: src/log/normal.c:58 +#: src/log/normal.c:57 #, 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:59 +#: src/log/normal.c:58 #, 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:60 +#: src/log/normal.c:59 #, 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:60 +#, fuzzy, c-format +msgid " Theory %1$s::%2$s failed with the following parameters: (%3$s)\n" +msgstr "" +" La théorie %1$s::%2$s a échoué avec les paramètres suivants: (%3$s)\n" + #: src/log/normal.c:61 +#, fuzzy, c-format +msgid "%1$s::%2$s: Timed out. (%3$3.2fs)\n" +msgstr "%1$s::%2$s: Délai expiré. (%3$3.2fs)\n" + +#: src/log/normal.c:62 #, 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:62 +#: src/log/normal.c:63 #, c-format msgid "%1$s::%2$s: CRASH!\n" msgstr "%1$s::%2$s: PLANTAGE!\n" -#: src/log/normal.c:63 +#: src/log/normal.c:64 #, fuzzy, c-format msgid "" "%1$sWarning! The test `%2$s::%3$s` crashed during its setup or teardown." @@ -74,14 +85,22 @@ msgstr "" "%1$sAttention! Le test `%2$s::%3$s` a planté pendant son initialisation ou " "sa finalisation.%4$s\n" -#: src/log/normal.c:64 +#: src/log/normal.c:65 +#, fuzzy, c-format +msgid "" +"%1$sWarning! The test `%2$s::%3$s` exited during its setup or teardown.%4$s\n" +msgstr "" +"%1$sAttention! Le test `%2$s::%3$s` a quitté pendant son initialisation ou " +"sa finalisation.%4$s\n" + +#: src/log/normal.c:66 #, c-format msgid "Running %1$s%2$lu%3$s test from %4$s%5$s%6$s:\n" msgid_plural "Running %1$s%2$lu%3$s tests from %4$s%5$s%6$s:\n" msgstr[0] "Lancement de %1$s%2$lu%3$s test dans %4$s%5$s%6$s:\n" msgstr[1] "Lancement de %1$s%2$lu%3$s tests dans %4$s%5$s%6$s:\n" -#: src/log/normal.c:66 +#: src/log/normal.c:68 #, c-format msgid "" "%1$sSynthesis: Tested: %2$s%3$lu%4$s | Passing: %5$s%6$lu%7$s | Failing: %8$s" @@ -89,3 +108,69 @@ msgid "" msgstr "" "%1$sSynthèse: Testés: %2$s%3$lu%4$s | Validés: %5$s%6$lu%7$s | Échoués: %8$s" "%9$lu%10$s | Plantages: %11$s%12$lu%13$s %14$s\n" + +#: src/i18n.c:13 +msgid "The conditions for this assertion were not met." +msgstr "Les conditions de cette assertion n'ont pas été remplies." + +#: src/i18n.c:14 +#, c-format +msgid "The expression %s is false." +msgstr "L'expression %s est fausse." + +#: src/i18n.c:15 +#, c-format +msgid "The expression (as strings) %s is false." +msgstr "L'expression (en tant que chaînes de caractères) %s est fausse." + +#: src/i18n.c:16 +#, c-format +msgid "%s is null." +msgstr "%s est nul." + +#: src/i18n.c:17 +#, c-format +msgid "%s is not null." +msgstr "%s n'est pas nul." + +#: src/i18n.c:18 +#, c-format +msgid "%s is empty." +msgstr "%s est vide." + +#: src/i18n.c:19 +#, c-format +msgid "%s is not empty." +msgstr "%s n'est pas vide." + +#: src/i18n.c:20 +#, fuzzy, c-format +msgid "The statement `%s` did not throw any exception." +msgstr "L'instruction `%s` n'a pas levé d'exception." + +#: src/i18n.c:21 +#, fuzzy, c-format +msgid "The statement `%s` threw some exception." +msgstr "L'instruction `%1$s` a levé une exception." + +#: src/i18n.c:24 +#, c-format +msgid "The file contents of %1$s does not match the string \"%2$s\"." +msgstr "" +"Le contenu du fichier %1$s ne correspond pas à la chaine de caractères \"%2$s" +"\"." + +#: src/i18n.c:25 +#, fuzzy, c-format +msgid "The file contents of %1$s does not match the contents of %2$s." +msgstr "Le contenu du fichier %1$s ne correspond pas au contenu de %2$s." + +#: src/i18n.c:26 +#, c-format +msgid "The statement `%1$s` did throw an instance of the `%2$s` exception." +msgstr "L'instruction `%1$s` a levé une instance de l'exception `%2$s`." + +#: src/i18n.c:27 +#, c-format +msgid "The statement `%1$s` did not throw an instance of the `%2$s` exception." +msgstr "L'instruction `%1$s` n'a pas levé d'instance de l'exception `%2$s`." diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 4f928b2e..54990fe3 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,20 +1,33 @@ -project(criterion_samples) - -set(CMAKE_C_FLAGS "-std=c99 -Wall -Wextra -pedantic") +if (NOT MSVC) + set(CMAKE_C_FLAGS "-std=c99 -Wall -Wextra -pedantic") + set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wextra -pedantic") +endif () include_directories(../include) set(SAMPLES - signal - report - suites - fixtures - asserts - more-suites - long-messages - description - other-crashes - simple + signal.c + report.c + suites.c + fixtures.c + asserts.c + more-suites.c + description.c + simple.c + theories.c + timeout.c + redirect.c + + signal.cc + report.cc + suites.cc + fixtures.cc + asserts.cc + more-suites.cc + description.cc + simple.cc + theories.cc + redirect.cc ) set(SCRIPTS @@ -22,26 +35,41 @@ set(SCRIPTS early_exit verbose list - pattern fail_fast help ) -foreach(sample ${SAMPLES}) - add_executable(${sample} ${sample}.c) - target_link_libraries(${sample} criterion) - add_test(${sample} ${sample}) - set_property(TEST ${sample} PROPERTY - ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1" - ) +if (HAVE_PCRE) + set(SCRIPTS ${SCRIPTS} pattern) +endif () - add_test(${sample}_compare sh ${CMAKE_CURRENT_LIST_DIR}/tests/run_test.sh "${CMAKE_CURRENT_LIST_DIR}" . . ${sample}) - set_property(TEST ${sample}_compare PROPERTY - ENVIRONMENT "LC_ALL=en_US.utf8" - ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1" - ENVIRONMENT "CRITERION_SHORT_FILENAME=1" - ) -endforeach() +add_custom_target(criterion_samples) +add_dependencies(criterion_tests criterion_samples) + +macro(add_samples DIR_ SAMPLES_) + foreach(sample ${SAMPLES_}) + add_executable(${sample}.bin EXCLUDE_FROM_ALL ${sample}) + add_dependencies(criterion_samples ${sample}.bin) + target_link_libraries(${sample}.bin criterion) + add_test(${sample} ${sample}.bin) + set_property(TEST ${sample} PROPERTY + ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1" + ) + + if (NOT MSVC) # we disable the scripted tests when building with MSVC + add_test(${sample}_compare sh ${DIR_}/run_test.sh "${CMAKE_CURRENT_LIST_DIR}/outputs" . . ${sample}.bin) + set_property(TEST ${sample}_compare PROPERTY + ENVIRONMENT "LC_ALL=en_US.utf8" + ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1" + ENVIRONMENT "CRITERION_SHORT_FILENAME=1" + ) + endif () + endforeach() +endmacro() + +add_samples("${CMAKE_CURRENT_LIST_DIR}/tests" "${SAMPLES}") + +if (NOT MSVC) # we disable the scripted tests when building with MSVC foreach(script ${SCRIPTS}) add_test(${script} sh ${CMAKE_CURRENT_LIST_DIR}/tests/${script}.sh) @@ -49,10 +77,14 @@ foreach(script ${SCRIPTS}) ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1" ) - add_test(${script}_compare sh ${CMAKE_CURRENT_LIST_DIR}/tests/run_test.sh "${CMAKE_CURRENT_LIST_DIR}" . "${CMAKE_CURRENT_LIST_DIR}" tests/${sample}) + add_test(${script}_compare sh ${CMAKE_CURRENT_LIST_DIR}/tests/run_test.sh "${CMAKE_CURRENT_LIST_DIR}" . "${CMAKE_CURRENT_LIST_DIR}" tests/${script}) set_property(TEST ${script}_compare PROPERTY ENVIRONMENT "LC_ALL=en_US.utf8" ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1" ENVIRONMENT "CRITERION_SHORT_FILENAME=1" ) endforeach() + +endif() + +add_subdirectory(tests) diff --git a/samples/Makefile.am b/samples/Makefile.am deleted file mode 100644 index 7ed5f121..00000000 --- a/samples/Makefile.am +++ /dev/null @@ -1,39 +0,0 @@ -BIN_TESTS = \ - signal \ - report \ - suites \ - fixtures \ - asserts \ - more-suites \ - long-messages \ - description \ - other-crashes \ - simple - -TESTS_ENVIRONMENT = CRITERION_ALWAYS_SUCCEED=1 - -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 \ - tests/list.sh \ - tests/pattern.sh \ - tests/fail_fast.sh \ - tests/help.sh - -EXTRA_DIST = $(SCRIPT_TESTS) - -tests/tap_test.sh: simple signal asserts -tests/early_exit.sh: simple -tests/verbose.sh: simple -tests/help.sh: simple - -TESTS = $(BIN_TESTS) $(SCRIPT_TESTS) diff --git a/samples/asserts.c b/samples/asserts.c index 627f7787..1d5d83e3 100644 --- a/samples/asserts.c +++ b/samples/asserts.c @@ -6,7 +6,7 @@ Test(asserts, base) { cr_assert(true, "Assertions may take failure messages"); - cr_assert(true, .msg = "You can use explicit named arguments"); + cr_assert(true, "Or even %d format string %s", 1, "with parameters"); cr_expect(false, "assert is fatal, expect isn't"); cr_assert(false, "This assert runs"); @@ -14,23 +14,24 @@ Test(asserts, base) { } Test(asserts, old_school) { - if (false) - cr_abort_test("You can abort the test with a message from anywhere"); - - cr_abort_test(NULL); // or without a message + cr_expect_fail("You can fail an assertion with a message from anywhere"); + cr_assert_fail(); // or without a message } Test(asserts, string) { - cr_assert_strings_eq("hello", "hello"); - cr_assert_strings_neq("hello", "olleh"); + cr_assert_str_empty(""); + cr_assert_str_not_empty("foo"); + + cr_assert_str_eq("hello", "hello"); + cr_assert_str_neq("hello", "olleh"); - cr_assert_strings_gt("hello", "hell"); - cr_assert_strings_geq("hello", "hell"); - cr_assert_strings_geq("hello", "hello"); + cr_assert_str_gt("hello", "hell"); + cr_assert_str_geq("hello", "hell"); + cr_assert_str_geq("hello", "hello"); - cr_assert_strings_lt("hell", "hello"); - cr_assert_strings_leq("hell", "hello"); - cr_assert_strings_leq("hello", "hello"); + cr_assert_str_lt("hell", "hello"); + cr_assert_str_leq("hell", "hello"); + cr_assert_str_leq("hello", "hello"); } Test(asserts, native) { @@ -64,6 +65,10 @@ Test(asserts, array) { int arr1[] = {1, 2, 3, 4}; int arr2[] = {4, 3, 2, 1}; + cr_assert_arr_eq(arr1, arr1, 4); + cr_assert_arr_neq(arr1, arr2, 4); + +#ifdef __GNUC__ struct dummy_struct s1[] = {{4, 2}, {2, 4}}; struct dummy_struct s2[2]; memset(s2, 0xFF, sizeof(s2)); @@ -72,9 +77,7 @@ Test(asserts, array) { s2[1].a = 2; s2[1].b = 4; - cr_assert_arrays_eq(arr1, arr1, 4); - cr_assert_arrays_neq(arr1, arr2, 4); - - cr_assert_arrays_neq(s1, s2, 2); - cr_assert_arrays_eq_cmp(s1, s2, 2, eq_dummy); + // cr_assert_arr_eq(s1, s2, 2); not guaranteed to work on structs. + cr_assert_arr_eq_cmp(s1, s2, 2, eq_dummy); +#endif } diff --git a/samples/asserts.cc b/samples/asserts.cc new file mode 100644 index 00000000..46c22afe --- /dev/null +++ b/samples/asserts.cc @@ -0,0 +1,90 @@ +#include +#include +#include + +Test(asserts, base) { + cr_assert(true); + cr_expect(true); + + cr_assert(true, "Assertions may take failure messages"); + + cr_assert(true, "Or even %d format string %s", 1, "with parameters"); + + cr_expect(false, "assert is fatal, expect isn't"); + cr_assert(false, "This assert runs"); + cr_assert(false, "This does not"); +} + +Test(asserts, old_school) { + cr_expect_fail("You can fail an assertion with a message from anywhere"); + cr_assert_fail(); // or without a message +} + +Test(asserts, string) { + cr_assert_str_empty(""); + cr_assert_str_not_empty("foo"); + + cr_assert_str_eq("hello", "hello"); + cr_assert_str_neq("hello", "olleh"); + + cr_assert_str_gt("hello", "hell"); + cr_assert_str_geq("hello", "hell"); + cr_assert_str_geq("hello", "hello"); + + cr_assert_str_lt("hell", "hello"); + cr_assert_str_leq("hell", "hello"); + cr_assert_str_leq("hello", "hello"); +} + +Test(asserts, native) { + cr_assert_eq(1, 1); + cr_assert_neq(1, 2); + + cr_assert_lt(1, 2); + cr_assert_leq(1, 2); + cr_assert_leq(2, 2); + + cr_assert_gt(2, 1); + cr_assert_geq(2, 1); + cr_assert_geq(2, 2); +} + +Test(asserts, float) { + cr_assert_neq(0.1 * 0.1, 0.01); + cr_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}; + + cr_assert_arr_eq(arr1, arr1, 4); + cr_assert_arr_neq(arr1, arr2, 4); + +#ifdef __GNUC__ + 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; + + // cr_assert_arrays_eq(&s1, &s2, 2); not guaranteed to work on structs. + cr_assert_arr_eq_cmp(&s1, &s2, 2, eq_dummy); +#endif +} + +Test(asserts, exception) { + cr_assert_throw(throw std::exception(), std::exception); + cr_assert_throw(throw std::exception(), std::bad_alloc); +} diff --git a/samples/description.cc b/samples/description.cc new file mode 100644 index 00000000..55b7b49c --- /dev/null +++ b/samples/description.cc @@ -0,0 +1,8 @@ +#include + +Test(misc, failing, .description = "Just a failing test") { + cr_assert(0); +} + +Test(misc, skipped, .description = "This one is skipped", .disabled = true) { +} diff --git a/samples/fixtures.cc b/samples/fixtures.cc new file mode 100644 index 00000000..1f7851c4 --- /dev/null +++ b/samples/fixtures.cc @@ -0,0 +1,14 @@ +#include +#include + +void setup(void) { + puts("Runs before the test"); +} + +void teardown(void) { + puts("Runs after the test"); +} + +Test(simple, fixtures, .init = setup, .fini = teardown) { + cr_assert(1); +} diff --git a/samples/more-suites.cc b/samples/more-suites.cc new file mode 100644 index 00000000..95d70393 --- /dev/null +++ b/samples/more-suites.cc @@ -0,0 +1,21 @@ +#include + +void setup_suite(void) { +} + +void teardown_suite(void) { +} + +TestSuite(suite1, .init = setup_suite, .fini = teardown_suite); + +Test(suite1, test) { + cr_assert(1); +} + +Test(suite2, test) { + cr_assert(1); +} + +TestSuite(disabled, .disabled = true); + +Test(disabled, test) {} diff --git a/samples/asserts.err.expected b/samples/outputs/asserts.c.bin.err.expected similarity index 63% rename from samples/asserts.err.expected rename to samples/outputs/asserts.c.bin.err.expected index 644a4407..95d9a944 100644 --- a/samples/asserts.err.expected +++ b/samples/outputs/asserts.c.bin.err.expected @@ -1,6 +1,7 @@ [----] asserts.c:11: Assertion failed: assert is fatal, expect isn't [----] asserts.c:12: Assertion failed: This assert runs [FAIL] asserts::base: (0.00s) -[----] asserts.c:20: Assertion failed: The conditions for this test were not met. +[----] asserts.c:17: Assertion failed: You can fail an assertion with a message from anywhere +[----] asserts.c:18: Assertion failed: The conditions for this assertion were not met. [FAIL] asserts::old_school: (0.00s) [====] Synthesis: Tested: 6 | Passing: 4 | Failing: 2 | Crashing: 0  diff --git a/samples/asserts.out.expected b/samples/outputs/asserts.c.bin.out.expected similarity index 100% rename from samples/asserts.out.expected rename to samples/outputs/asserts.c.bin.out.expected diff --git a/samples/outputs/asserts.cc.bin.err.expected b/samples/outputs/asserts.cc.bin.err.expected new file mode 100644 index 00000000..ebeb3553 --- /dev/null +++ b/samples/outputs/asserts.cc.bin.err.expected @@ -0,0 +1,11 @@ +[----] asserts.cc:83: Assertion failed: The expression (&s1)[0 .. 2] == (&s2)[0 .. 2] is false. +[FAIL] asserts::array: (0.00s) +[----] asserts.cc:13: Assertion failed: assert is fatal, expect isn't +[----] asserts.cc:14: Assertion failed: This assert runs +[FAIL] asserts::base: (0.00s) +[----] asserts.cc:89: Assertion failed: The statement `throw std::exception()` did not throw an instance of the `std::bad_alloc` exception. +[FAIL] asserts::exception: (0.00s) +[----] asserts.cc:19: Assertion failed: You can fail an assertion with a message from anywhere +[----] asserts.cc:20: Assertion failed: The conditions for this assertion were not met. +[FAIL] asserts::old_school: (0.00s) +[====] Synthesis: Tested: 7 | Passing: 3 | Failing: 4 | Crashing: 0  diff --git a/samples/description.out.expected b/samples/outputs/asserts.cc.bin.out.expected similarity index 100% rename from samples/description.out.expected rename to samples/outputs/asserts.cc.bin.out.expected diff --git a/samples/description.err.expected b/samples/outputs/description.c.bin.err.expected similarity index 86% rename from samples/description.err.expected rename to samples/outputs/description.c.bin.err.expected index 64ffe215..2cc0d104 100644 --- a/samples/description.err.expected +++ b/samples/outputs/description.c.bin.err.expected @@ -1,3 +1,3 @@ -[----] description.c:4: Assertion failed: 0 +[----] description.c:4: Assertion failed: The expression 0 is false. [FAIL] misc::failing: (0.00s) [====] Synthesis: Tested: 1 | Passing: 0 | Failing: 1 | Crashing: 0  diff --git a/samples/long-messages.out.expected b/samples/outputs/description.c.bin.out.expected similarity index 100% rename from samples/long-messages.out.expected rename to samples/outputs/description.c.bin.out.expected diff --git a/samples/outputs/description.cc.bin.err.expected b/samples/outputs/description.cc.bin.err.expected new file mode 100644 index 00000000..b9cebe4b --- /dev/null +++ b/samples/outputs/description.cc.bin.err.expected @@ -0,0 +1,3 @@ +[----] description.cc:4: Assertion failed: The expression 0 is false. +[FAIL] misc::failing: (0.00s) +[====] Synthesis: Tested: 1 | Passing: 0 | Failing: 1 | Crashing: 0  diff --git a/samples/more-suites.out.expected b/samples/outputs/description.cc.bin.out.expected similarity index 100% rename from samples/more-suites.out.expected rename to samples/outputs/description.cc.bin.out.expected diff --git a/samples/fixtures.err.expected b/samples/outputs/fixtures.c.bin.err.expected similarity index 100% rename from samples/fixtures.err.expected rename to samples/outputs/fixtures.c.bin.err.expected diff --git a/samples/fixtures.out.expected b/samples/outputs/fixtures.c.bin.out.expected similarity index 100% rename from samples/fixtures.out.expected rename to samples/outputs/fixtures.c.bin.out.expected diff --git a/samples/outputs/fixtures.cc.bin.err.expected b/samples/outputs/fixtures.cc.bin.err.expected new file mode 100644 index 00000000..a34f221b --- /dev/null +++ b/samples/outputs/fixtures.cc.bin.err.expected @@ -0,0 +1 @@ +[====] Synthesis: Tested: 1 | Passing: 1 | Failing: 0 | Crashing: 0  diff --git a/samples/outputs/fixtures.cc.bin.out.expected b/samples/outputs/fixtures.cc.bin.out.expected new file mode 100644 index 00000000..a74afed9 --- /dev/null +++ b/samples/outputs/fixtures.cc.bin.out.expected @@ -0,0 +1,2 @@ +Runs before the test +Runs after the test diff --git a/samples/more-suites.err.expected b/samples/outputs/more-suites.c.bin.err.expected similarity index 100% rename from samples/more-suites.err.expected rename to samples/outputs/more-suites.c.bin.err.expected diff --git a/samples/other-crashes.out.expected b/samples/outputs/more-suites.c.bin.out.expected similarity index 100% rename from samples/other-crashes.out.expected rename to samples/outputs/more-suites.c.bin.out.expected diff --git a/samples/suites.err.expected b/samples/outputs/more-suites.cc.bin.err.expected similarity index 100% rename from samples/suites.err.expected rename to samples/outputs/more-suites.cc.bin.err.expected diff --git a/samples/signal.out.expected b/samples/outputs/more-suites.cc.bin.out.expected similarity index 100% rename from samples/signal.out.expected rename to samples/outputs/more-suites.cc.bin.out.expected diff --git a/samples/other-crashes.err.expected b/samples/outputs/other-crashes.c.bin.err.expected similarity index 100% rename from samples/other-crashes.err.expected rename to samples/outputs/other-crashes.c.bin.err.expected diff --git a/samples/simple.out.expected b/samples/outputs/other-crashes.c.bin.out.expected similarity index 100% rename from samples/simple.out.expected rename to samples/outputs/other-crashes.c.bin.out.expected diff --git a/samples/outputs/other-crashes.cc.bin.err.expected b/samples/outputs/other-crashes.cc.bin.err.expected new file mode 100644 index 00000000..41d3cb89 --- /dev/null +++ b/samples/outputs/other-crashes.cc.bin.err.expected @@ -0,0 +1,3 @@ +[----] Warning! The test `misc::setup_crash` crashed during its setup or teardown. +[----] Warning! The test `misc::teardown_crash` crashed during its setup or teardown. +[====] Synthesis: Tested: 2 | Passing: 1 | Failing: 1 | Crashing: 1  diff --git a/samples/suites.out.expected b/samples/outputs/other-crashes.cc.bin.out.expected similarity index 100% rename from samples/suites.out.expected rename to samples/outputs/other-crashes.cc.bin.out.expected diff --git a/samples/outputs/redirect.c.bin.err.expected b/samples/outputs/redirect.c.bin.err.expected new file mode 100644 index 00000000..09eb50f6 --- /dev/null +++ b/samples/outputs/redirect.c.bin.err.expected @@ -0,0 +1 @@ +[====] Synthesis: Tested: 2 | Passing: 2 | Failing: 0 | Crashing: 0  diff --git a/samples/outputs/redirect.c.bin.out.expected b/samples/outputs/redirect.c.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/outputs/redirect.cc.bin.err.expected b/samples/outputs/redirect.cc.bin.err.expected new file mode 100644 index 00000000..09eb50f6 --- /dev/null +++ b/samples/outputs/redirect.cc.bin.err.expected @@ -0,0 +1 @@ +[====] Synthesis: Tested: 2 | Passing: 2 | Failing: 0 | Crashing: 0  diff --git a/samples/outputs/redirect.cc.bin.out.expected b/samples/outputs/redirect.cc.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/report.err.expected b/samples/outputs/report.c.bin.err.expected similarity index 88% rename from samples/report.err.expected rename to samples/outputs/report.c.bin.err.expected index 5b8f140b..f08bf9aa 100644 --- a/samples/report.err.expected +++ b/samples/outputs/report.c.bin.err.expected @@ -1,3 +1,3 @@ -[----] report.c:5: Assertion failed: 0 +[----] report.c:5: Assertion failed: The expression 0 is false. [FAIL] sample::test: (0.00s) [====] Synthesis: Tested: 1 | Passing: 0 | Failing: 1 | Crashing: 0  diff --git a/samples/report.out.expected b/samples/outputs/report.c.bin.out.expected similarity index 100% rename from samples/report.out.expected rename to samples/outputs/report.c.bin.out.expected diff --git a/samples/outputs/report.cc.bin.err.expected b/samples/outputs/report.cc.bin.err.expected new file mode 100644 index 00000000..72c59aa4 --- /dev/null +++ b/samples/outputs/report.cc.bin.err.expected @@ -0,0 +1,3 @@ +[----] report.cc:5: Assertion failed: The expression 0 is false. +[FAIL] sample::test: (0.00s) +[====] Synthesis: Tested: 1 | Passing: 0 | Failing: 1 | Crashing: 0  diff --git a/samples/outputs/report.cc.bin.out.expected b/samples/outputs/report.cc.bin.out.expected new file mode 100644 index 00000000..d560277c --- /dev/null +++ b/samples/outputs/report.cc.bin.out.expected @@ -0,0 +1,4 @@ +criterion_init +testing test in category sample +Asserts: [1 passed, 1 failed, 2 total] +criterion_fini diff --git a/samples/signal.err.expected b/samples/outputs/signal.c.bin.err.expected similarity index 100% rename from samples/signal.err.expected rename to samples/outputs/signal.c.bin.err.expected diff --git a/samples/outputs/signal.c.bin.out.expected b/samples/outputs/signal.c.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/outputs/signal.cc.bin.err.expected b/samples/outputs/signal.cc.bin.err.expected new file mode 100644 index 00000000..8ac44c18 --- /dev/null +++ b/samples/outputs/signal.cc.bin.err.expected @@ -0,0 +1,4 @@ +[----] signal.cc:16: Unexpected signal caught below this line! +[FAIL] simple::uncaught: CRASH! +[FAIL] simple::wrong_signal: (0.00s) +[====] Synthesis: Tested: 3 | Passing: 1 | Failing: 2 | Crashing: 1  diff --git a/samples/outputs/signal.cc.bin.out.expected b/samples/outputs/signal.cc.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/simple.err.expected b/samples/outputs/simple.c.bin.err.expected similarity index 88% rename from samples/simple.err.expected rename to samples/outputs/simple.c.bin.err.expected index ef375ca7..a6878985 100644 --- a/samples/simple.err.expected +++ b/samples/outputs/simple.c.bin.err.expected @@ -1,3 +1,3 @@ -[----] simple.c:4: Assertion failed: 0 +[----] simple.c:4: Assertion failed: The expression 0 is false. [FAIL] misc::failing: (0.00s) [====] Synthesis: Tested: 2 | Passing: 1 | Failing: 1 | Crashing: 0  diff --git a/samples/outputs/simple.c.bin.out.expected b/samples/outputs/simple.c.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/outputs/simple.cc.bin.err.expected b/samples/outputs/simple.cc.bin.err.expected new file mode 100644 index 00000000..816d6cd4 --- /dev/null +++ b/samples/outputs/simple.cc.bin.err.expected @@ -0,0 +1,3 @@ +[----] simple.cc:4: Assertion failed: The expression 0 is false. +[FAIL] misc::failing: (0.00s) +[====] Synthesis: Tested: 2 | Passing: 1 | Failing: 1 | Crashing: 0  diff --git a/samples/outputs/simple.cc.bin.out.expected b/samples/outputs/simple.cc.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/outputs/suites.c.bin.err.expected b/samples/outputs/suites.c.bin.err.expected new file mode 100644 index 00000000..09eb50f6 --- /dev/null +++ b/samples/outputs/suites.c.bin.err.expected @@ -0,0 +1 @@ +[====] Synthesis: Tested: 2 | Passing: 2 | Failing: 0 | Crashing: 0  diff --git a/samples/outputs/suites.c.bin.out.expected b/samples/outputs/suites.c.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/outputs/suites.cc.bin.err.expected b/samples/outputs/suites.cc.bin.err.expected new file mode 100644 index 00000000..09eb50f6 --- /dev/null +++ b/samples/outputs/suites.cc.bin.err.expected @@ -0,0 +1 @@ +[====] Synthesis: Tested: 2 | Passing: 2 | Failing: 0 | Crashing: 0  diff --git a/samples/outputs/suites.cc.bin.out.expected b/samples/outputs/suites.cc.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/redirect.c b/samples/redirect.c new file mode 100644 index 00000000..9a0db123 --- /dev/null +++ b/samples/redirect.c @@ -0,0 +1,52 @@ +#include +#include + +#include +#include + +// Testing stdout/stderr + +void redirect_all_std(void) { + cr_redirect_stdout(); + cr_redirect_stderr(); +} + +Test(redirect, test_outputs, .init = redirect_all_std) { + fprintf(stdout, "foo"); + fflush(stdout); + + cr_assert_stdout_eq_str("foo"); + + fprintf(stderr, "bar"); + fflush(stderr); + + cr_assert_stderr_eq_str("bar"); +} + +// Testing general I/O with sample command-line rot13 + +char rot13_char(char c) { + return isalpha(c) ? (c - 'a' + 13) % 26 + 'a' : c; +} + +void rot13_io(void) { + char buf[512]; + + size_t read; + while ((read = fread(buf, 1, sizeof (buf), stdin)) > 0) { + for (size_t i = 0; i < read; ++i) + buf[i] = rot13_char(buf[i]); + fwrite(buf, 1, read, stdout); + } + fflush(stdout); +} + +Test(redirect, rot13, .init = cr_redirect_stdout) { + FILE* f_stdin = cr_get_redirected_stdin(); + fprintf(f_stdin, "the quick brown fox jumps over the lazy dog"); + fclose(f_stdin); + + rot13_io(); + + cr_assert_stdout_eq_str("gur dhvpx oebja sbk whzcf bire gur ynml qbt"); +} diff --git a/samples/redirect.cc b/samples/redirect.cc new file mode 100644 index 00000000..87a25da0 --- /dev/null +++ b/samples/redirect.cc @@ -0,0 +1,52 @@ +#include +#include + +#include +#include +#include +#include + +#ifdef __GNUC__ +# include +#endif + +// Testing stdout/stderr + +void redirect_all_std(void) { + cr_redirect_stdout(); + cr_redirect_stderr(); +} + +Test(redirect, test_outputs, .init = redirect_all_std) { + std::cout << "foo" << std::flush; + std::cerr << "bar" << std::flush; + + cr_assert_stdout_eq_str("foo"); + cr_assert_stderr_eq_str("bar"); +} + +// Testing general I/O with sample command-line rot13 + +char rot13_char(char c) { + return std::isalpha(c) ? (c - 'a' + 13) % 26 + 'a' : c; +} + +void rot13_io(void) { + std::string s; + + std::cin >> s; + for (size_t i = 0; i < s.length(); ++i) + s[i] = rot13_char(s[i]); + std::cout << s << std::flush; +} + +Test(redirect, rot13, .init = cr_redirect_stdout) { + auto& f_cin = criterion::get_redirected_cin(); + + f_cin << "the quick brown fox jumps over the lazy dog"; + f_cin.close(); + + rot13_io(); + + cr_assert_stdout_eq_str("gur dhvpx oebja sbk whzcf bire gur ynml qbt"); +} diff --git a/samples/report.c b/samples/report.c index 4c7a8499..8c9ace8a 100644 --- a/samples/report.c +++ b/samples/report.c @@ -15,10 +15,12 @@ ReportHook(POST_TEST)(struct criterion_test_stats *stats) { stats->passed_asserts, stats->failed_asserts, stats->passed_asserts + stats->failed_asserts); } -ReportHook(PRE_ALL)() { +ReportHook(PRE_ALL)(struct criterion_test_set *tests) { + (void) tests; puts("criterion_init"); } -ReportHook(POST_ALL)() { +ReportHook(POST_ALL)(struct criterion_global_stats *stats) { + (void) stats; puts("criterion_fini"); } diff --git a/samples/report.cc b/samples/report.cc new file mode 100644 index 00000000..8c9ace8a --- /dev/null +++ b/samples/report.cc @@ -0,0 +1,26 @@ +#include +#include + +Test(sample, test) { + cr_expect(0); + cr_assert(1); +} + +ReportHook(PRE_INIT)(struct criterion_test *test) { + printf("testing %s in category %s\n", test->name, test->category); +} + +ReportHook(POST_TEST)(struct criterion_test_stats *stats) { + printf("Asserts: [%d passed, %d failed, %d total]\n", + stats->passed_asserts, stats->failed_asserts, stats->passed_asserts + stats->failed_asserts); +} + +ReportHook(PRE_ALL)(struct criterion_test_set *tests) { + (void) tests; + puts("criterion_init"); +} + +ReportHook(POST_ALL)(struct criterion_global_stats *stats) { + (void) stats; + puts("criterion_fini"); +} diff --git a/samples/signal.cc b/samples/signal.cc new file mode 100644 index 00000000..9dcc99d3 --- /dev/null +++ b/samples/signal.cc @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +Test(simple, caught, .signal = SIGSEGV) { + int *i = NULL; + *i = 42; +} + +Test(simple, wrong_signal, .signal = SIGINT) { + int *i = NULL; + *i = 42; +} + +Test(simple, uncaught) { + int *i = NULL; + *i = 42; +} diff --git a/samples/simple.cc b/samples/simple.cc new file mode 100644 index 00000000..d5916f5a --- /dev/null +++ b/samples/simple.cc @@ -0,0 +1,9 @@ +#include + +Test(misc, failing) { + cr_assert(0); +} + +Test(misc, passing) { + cr_assert(1); +} diff --git a/samples/suites.cc b/samples/suites.cc new file mode 100644 index 00000000..d19d7b2d --- /dev/null +++ b/samples/suites.cc @@ -0,0 +1,9 @@ +#include + +Test(first_suite, test) { + cr_assert(1); +} + +Test(second_suite, test) { + cr_assert(1); +} diff --git a/samples/tests/CMakeLists.txt b/samples/tests/CMakeLists.txt new file mode 100644 index 00000000..463d4da7 --- /dev/null +++ b/samples/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SAMPLES + failmessages.c + exit.c + long-messages.c + other-crashes.c + + failmessages.cc + exit.cc + long-messages.cc + other-crashes.cc +) + +add_samples("${CMAKE_CURRENT_LIST_DIR}" "${SAMPLES}") diff --git a/samples/tests/early_exit.sh b/samples/tests/early_exit.sh index 30f513a5..71bb31d1 100755 --- a/samples/tests/early_exit.sh +++ b/samples/tests/early_exit.sh @@ -1,2 +1,3 @@ #!/bin/sh -./simple --no-early-exit --always-succeed +./simple.c.bin --no-early-exit --always-succeed +./theories.c.bin --no-early-exit --always-succeed diff --git a/samples/tests/exit.c b/samples/tests/exit.c new file mode 100644 index 00000000..f9c95dd6 --- /dev/null +++ b/samples/tests/exit.c @@ -0,0 +1,24 @@ +#include +#include +#include + +Test(exit, normal, .exit_code = 0) { +} + +Test(exit, expected_exit, .exit_code = 42) { + exit(42); +} + +Test(exit, unexpected_exit) { + exit(127); +} + +void do_exit (void) { + exit(127); +} + +Test(exit_with_fixtures, init_exits, .init = do_exit) { +} + +Test(exit_with_fixtures, fini_exits, .fini = do_exit) { +} diff --git a/samples/tests/exit.cc b/samples/tests/exit.cc new file mode 100644 index 00000000..f9c95dd6 --- /dev/null +++ b/samples/tests/exit.cc @@ -0,0 +1,24 @@ +#include +#include +#include + +Test(exit, normal, .exit_code = 0) { +} + +Test(exit, expected_exit, .exit_code = 42) { + exit(42); +} + +Test(exit, unexpected_exit) { + exit(127); +} + +void do_exit (void) { + exit(127); +} + +Test(exit_with_fixtures, init_exits, .init = do_exit) { +} + +Test(exit_with_fixtures, fini_exits, .fini = do_exit) { +} diff --git a/samples/tests/fail_fast.sh b/samples/tests/fail_fast.sh index 3fecfd8c..fcc02e17 100755 --- a/samples/tests/fail_fast.sh +++ b/samples/tests/fail_fast.sh @@ -1,2 +1,2 @@ #!/bin/sh -./simple --fail-fast --always-succeed +./simple.c.bin --fail-fast --always-succeed diff --git a/samples/tests/failmessages.c b/samples/tests/failmessages.c new file mode 100644 index 00000000..e2bc221b --- /dev/null +++ b/samples/tests/failmessages.c @@ -0,0 +1,49 @@ +#include + +Test(messages, default) { + cr_expect(0); + cr_expect_eq(0, 1); + cr_expect_neq(1, 1); + cr_expect_lt(2, 1); + cr_expect_leq(2, 1); + cr_expect_gt(1, 2); + cr_expect_geq(1, 2); + cr_expect_null(""); + cr_expect_not_null(NULL); + + cr_expect_float_eq(1, 2, 0.1); + cr_expect_float_neq(2, 2, 0.1); + + cr_expect_str_empty("foo"); + cr_expect_str_not_empty(""); + cr_expect_str_eq("abc", "abd"); + cr_expect_str_neq("abc", "abc"); + cr_expect_str_lt("abc", "aba"); + cr_expect_str_leq("abc", "aba"); + cr_expect_str_gt("abc", "abd"); + cr_expect_str_geq("abc", "abd"); +} + +Test(messages, user) { + cr_expect(0, "foo %s", "bar"); + cr_expect_eq(0, 1, "foo %s", "bar"); + cr_expect_neq(1, 1, "foo %s", "bar"); + cr_expect_lt(2, 1, "foo %s", "bar"); + cr_expect_leq(2, 1, "foo %s", "bar"); + cr_expect_gt(1, 2, "foo %s", "bar"); + cr_expect_geq(1, 2, "foo %s", "bar"); + cr_expect_null("", "foo %s", "bar"); + cr_expect_not_null(NULL, "foo %s", "bar"); + + cr_expect_float_eq(1, 2, 0.1, "foo %s", "bar"); + cr_expect_float_neq(2, 2, 0.1, "foo %s", "bar"); + + cr_expect_str_empty("foo", "foo %s", "bar"); + cr_expect_str_not_empty("", "foo %s", "bar"); + cr_expect_str_eq("abc", "abd", "foo %s", "bar"); + cr_expect_str_neq("abc", "abc", "foo %s", "bar"); + cr_expect_str_lt("abc", "aba", "foo %s", "bar"); + cr_expect_str_leq("abc", "aba", "foo %s", "bar"); + cr_expect_str_gt("abc", "abd", "foo %s", "bar"); + cr_expect_str_geq("abc", "abd", "foo %s", "bar"); +} diff --git a/samples/tests/failmessages.cc b/samples/tests/failmessages.cc new file mode 100644 index 00000000..6e57b648 --- /dev/null +++ b/samples/tests/failmessages.cc @@ -0,0 +1,60 @@ +#include +#include + +Test(messages, default) { + cr_expect(0); + cr_expect_eq(0, 1); + cr_expect_neq(1, 1); + cr_expect_lt(2, 1); + cr_expect_leq(2, 1); + cr_expect_gt(1, 2); + cr_expect_geq(1, 2); + cr_expect_null(""); + cr_expect_not_null(NULL); + + cr_expect_float_eq(1, 2, 0.1); + cr_expect_float_neq(2, 2, 0.1); + + cr_expect_str_empty("foo"); + cr_expect_str_not_empty(""); + cr_expect_str_eq("abc", "abd"); + cr_expect_str_neq("abc", "abc"); + cr_expect_str_lt("abc", "aba"); + cr_expect_str_leq("abc", "aba"); + cr_expect_str_gt("abc", "abd"); + cr_expect_str_geq("abc", "abd"); + + cr_expect_throw(throw std::exception(), std::bad_alloc); + cr_expect_no_throw(throw std::exception(), std::exception); + cr_expect_any_throw({}); + cr_expect_none_throw(throw std::exception()); +} + +Test(messages, user) { + cr_expect(0, "foo %s", "bar"); + cr_expect_eq(0, 1, "foo %s", "bar"); + cr_expect_neq(1, 1, "foo %s", "bar"); + cr_expect_lt(2, 1, "foo %s", "bar"); + cr_expect_leq(2, 1, "foo %s", "bar"); + cr_expect_gt(1, 2, "foo %s", "bar"); + cr_expect_geq(1, 2, "foo %s", "bar"); + cr_expect_null("", "foo %s", "bar"); + cr_expect_not_null(NULL, "foo %s", "bar"); + + cr_expect_float_eq(1, 2, 0.1, "foo %s", "bar"); + cr_expect_float_neq(2, 2, 0.1, "foo %s", "bar"); + + cr_expect_str_empty("foo", "foo %s", "bar"); + cr_expect_str_not_empty("", "foo %s", "bar"); + cr_expect_str_eq("abc", "abd", "foo %s", "bar"); + cr_expect_str_neq("abc", "abc", "foo %s", "bar"); + cr_expect_str_lt("abc", "aba", "foo %s", "bar"); + cr_expect_str_leq("abc", "aba", "foo %s", "bar"); + cr_expect_str_gt("abc", "abd", "foo %s", "bar"); + cr_expect_str_geq("abc", "abd", "foo %s", "bar"); + + cr_expect_throw(throw std::exception(), std::bad_alloc, "foo %s", "bar"); + cr_expect_no_throw(throw std::exception(), std::exception, "foo %s", "bar"); + cr_expect_any_throw({}, "foo %s", "bar"); + cr_expect_none_throw(throw std::exception(), "foo %s", "bar"); +} diff --git a/samples/tests/help.sh b/samples/tests/help.sh index 6b60bb51..6752cc60 100755 --- a/samples/tests/help.sh +++ b/samples/tests/help.sh @@ -1,3 +1,3 @@ #!/bin/sh -./simple --help -./simple --version +./simple.c.bin --help +./simple.c.bin --version diff --git a/samples/tests/list.sh b/samples/tests/list.sh index 2040e540..6ab72bf3 100755 --- a/samples/tests/list.sh +++ b/samples/tests/list.sh @@ -1,3 +1,3 @@ #!/bin/sh -./simple --list -./simple --list --ascii +./simple.c.bin --list +./simple.c.bin --list --ascii diff --git a/samples/long-messages.c b/samples/tests/long-messages.c similarity index 100% rename from samples/long-messages.c rename to samples/tests/long-messages.c diff --git a/samples/tests/long-messages.cc b/samples/tests/long-messages.cc new file mode 100644 index 00000000..02472e1b --- /dev/null +++ b/samples/tests/long-messages.cc @@ -0,0 +1,5 @@ +#include + +Test(sample, long_msg) { + cr_assert(0, "This is\nA long message\nSpawning multiple lines.\n\nFormatting is respected."); +} diff --git a/samples/other-crashes.c b/samples/tests/other-crashes.c similarity index 100% rename from samples/other-crashes.c rename to samples/tests/other-crashes.c diff --git a/samples/tests/other-crashes.cc b/samples/tests/other-crashes.cc new file mode 100644 index 00000000..ab689bb2 --- /dev/null +++ b/samples/tests/other-crashes.cc @@ -0,0 +1,14 @@ +#include + +void crash(void) { + int *i = NULL; + *i = 42; +} + +Test(misc, setup_crash, .init = crash) { + cr_assert(true); +} + +Test(misc, teardown_crash, .fini = crash) { + cr_assert(true); +} diff --git a/samples/tests/outputs/exit.c.bin.err.expected b/samples/tests/outputs/exit.c.bin.err.expected new file mode 100644 index 00000000..3ffe51b6 --- /dev/null +++ b/samples/tests/outputs/exit.c.bin.err.expected @@ -0,0 +1,4 @@ +[----] Warning! The test `exit::unexpected_exit` exited during its setup or teardown. +[----] Warning! The test `exit_with_fixtures::fini_exits` exited during its setup or teardown. +[----] Warning! The test `exit_with_fixtures::init_exits` exited during its setup or teardown. +[====] Synthesis: Tested: 5 | Passing: 3 | Failing: 2 | Crashing: 2  diff --git a/samples/tests/outputs/exit.c.bin.out.expected b/samples/tests/outputs/exit.c.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/tests/outputs/exit.cc.bin.err.expected b/samples/tests/outputs/exit.cc.bin.err.expected new file mode 100644 index 00000000..3ffe51b6 --- /dev/null +++ b/samples/tests/outputs/exit.cc.bin.err.expected @@ -0,0 +1,4 @@ +[----] Warning! The test `exit::unexpected_exit` exited during its setup or teardown. +[----] Warning! The test `exit_with_fixtures::fini_exits` exited during its setup or teardown. +[----] Warning! The test `exit_with_fixtures::init_exits` exited during its setup or teardown. +[====] Synthesis: Tested: 5 | Passing: 3 | Failing: 2 | Crashing: 2  diff --git a/samples/tests/outputs/exit.cc.bin.out.expected b/samples/tests/outputs/exit.cc.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/tests/outputs/failmessages.c.bin.err.expected b/samples/tests/outputs/failmessages.c.bin.err.expected new file mode 100644 index 00000000..3a6b0e7a --- /dev/null +++ b/samples/tests/outputs/failmessages.c.bin.err.expected @@ -0,0 +1,41 @@ +[----] failmessages.c:4: Assertion failed: The expression 0 is false. +[----] failmessages.c:5: Assertion failed: The expression (0) == (1) is false. +[----] failmessages.c:6: Assertion failed: The expression (1) != (1) is false. +[----] failmessages.c:7: Assertion failed: The expression (2) < (1) is false. +[----] failmessages.c:8: Assertion failed: The expression (2) <= (1) is false. +[----] failmessages.c:9: Assertion failed: The expression (1) > (2) is false. +[----] failmessages.c:10: Assertion failed: The expression (1) >= (2) is false. +[----] failmessages.c:11: Assertion failed: "" is not null. +[----] failmessages.c:12: Assertion failed: ((void *)0) is null. +[----] failmessages.c:14: Assertion failed: The expression (2) - (1) <= (0.1) && (1) - (2) <= (0.1) is false. +[----] failmessages.c:15: Assertion failed: The expression (2) - (2) > (0.1) || (2) - (2) > (0.1) is false. +[----] failmessages.c:17: Assertion failed: "foo" is not empty. +[----] failmessages.c:18: Assertion failed: "" is empty. +[----] failmessages.c:19: Assertion failed: The expression (as strings) ("abc") == ("abd") is false. +[----] failmessages.c:20: Assertion failed: The expression (as strings) ("abc") != ("abc") is false. +[----] failmessages.c:21: Assertion failed: The expression (as strings) ("abc") < ("aba") is false. +[----] failmessages.c:22: Assertion failed: The expression (as strings) ("abc") <= ("aba") is false. +[----] failmessages.c:23: Assertion failed: The expression (as strings) ("abc") > ("abd") is false. +[----] failmessages.c:24: Assertion failed: The expression (as strings) ("abc") >= ("abd") is false. +[FAIL] messages::default: (0.00s) +[----] failmessages.c:28: Assertion failed: foo bar +[----] failmessages.c:29: Assertion failed: foo bar +[----] failmessages.c:30: Assertion failed: foo bar +[----] failmessages.c:31: Assertion failed: foo bar +[----] failmessages.c:32: Assertion failed: foo bar +[----] failmessages.c:33: Assertion failed: foo bar +[----] failmessages.c:34: Assertion failed: foo bar +[----] failmessages.c:35: Assertion failed: foo bar +[----] failmessages.c:36: Assertion failed: foo bar +[----] failmessages.c:38: Assertion failed: foo bar +[----] failmessages.c:39: Assertion failed: foo bar +[----] failmessages.c:41: Assertion failed: foo bar +[----] failmessages.c:42: Assertion failed: foo bar +[----] failmessages.c:43: Assertion failed: foo bar +[----] failmessages.c:44: Assertion failed: foo bar +[----] failmessages.c:45: Assertion failed: foo bar +[----] failmessages.c:46: Assertion failed: foo bar +[----] failmessages.c:47: Assertion failed: foo bar +[----] failmessages.c:48: Assertion failed: foo bar +[FAIL] messages::user: (0.00s) +[====] Synthesis: Tested: 2 | Passing: 0 | Failing: 2 | Crashing: 0  diff --git a/samples/tests/outputs/failmessages.c.bin.out.expected b/samples/tests/outputs/failmessages.c.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/tests/outputs/failmessages.cc.bin.err.expected b/samples/tests/outputs/failmessages.cc.bin.err.expected new file mode 100644 index 00000000..f060bace --- /dev/null +++ b/samples/tests/outputs/failmessages.cc.bin.err.expected @@ -0,0 +1,49 @@ +[----] failmessages.cc:5: Assertion failed: The expression 0 is false. +[----] failmessages.cc:6: Assertion failed: The expression (0) == (1) is false. +[----] failmessages.cc:7: Assertion failed: The expression (1) != (1) is false. +[----] failmessages.cc:8: Assertion failed: The expression (2) < (1) is false. +[----] failmessages.cc:9: Assertion failed: The expression (2) <= (1) is false. +[----] failmessages.cc:10: Assertion failed: The expression (1) > (2) is false. +[----] failmessages.cc:11: Assertion failed: The expression (1) >= (2) is false. +[----] failmessages.cc:12: Assertion failed: "" is not null. +[----] failmessages.cc:13: Assertion failed: __null is null. +[----] failmessages.cc:15: Assertion failed: The expression (2) - (1) <= (0.1) && (1) - (2) <= (0.1) is false. +[----] failmessages.cc:16: Assertion failed: The expression (2) - (2) > (0.1) || (2) - (2) > (0.1) is false. +[----] failmessages.cc:18: Assertion failed: "foo" is not empty. +[----] failmessages.cc:19: Assertion failed: "" is empty. +[----] failmessages.cc:20: Assertion failed: The expression (as strings) ("abc") == ("abd") is false. +[----] failmessages.cc:21: Assertion failed: The expression (as strings) ("abc") != ("abc") is false. +[----] failmessages.cc:22: Assertion failed: The expression (as strings) ("abc") < ("aba") is false. +[----] failmessages.cc:23: Assertion failed: The expression (as strings) ("abc") <= ("aba") is false. +[----] failmessages.cc:24: Assertion failed: The expression (as strings) ("abc") > ("abd") is false. +[----] failmessages.cc:25: Assertion failed: The expression (as strings) ("abc") >= ("abd") is false. +[----] failmessages.cc:27: Assertion failed: The statement `throw std::exception()` did not throw an instance of the `std::bad_alloc` exception. +[----] failmessages.cc:28: Assertion failed: The statement `throw std::exception()` did throw an instance of the `std::exception` exception. +[----] failmessages.cc:29: Assertion failed: The statement `{}` did not throw any exception. +[----] failmessages.cc:30: Assertion failed: The statement `throw std::exception()` threw some exception. +[FAIL] messages::default: (0.00s) +[----] failmessages.cc:34: Assertion failed: foo bar +[----] failmessages.cc:35: Assertion failed: foo bar +[----] failmessages.cc:36: Assertion failed: foo bar +[----] failmessages.cc:37: Assertion failed: foo bar +[----] failmessages.cc:38: Assertion failed: foo bar +[----] failmessages.cc:39: Assertion failed: foo bar +[----] failmessages.cc:40: Assertion failed: foo bar +[----] failmessages.cc:41: Assertion failed: foo bar +[----] failmessages.cc:42: Assertion failed: foo bar +[----] failmessages.cc:44: Assertion failed: foo bar +[----] failmessages.cc:45: Assertion failed: foo bar +[----] failmessages.cc:47: Assertion failed: foo bar +[----] failmessages.cc:48: Assertion failed: foo bar +[----] failmessages.cc:49: Assertion failed: foo bar +[----] failmessages.cc:50: Assertion failed: foo bar +[----] failmessages.cc:51: Assertion failed: foo bar +[----] failmessages.cc:52: Assertion failed: foo bar +[----] failmessages.cc:53: Assertion failed: foo bar +[----] failmessages.cc:54: Assertion failed: foo bar +[----] failmessages.cc:56: Assertion failed: foo bar +[----] failmessages.cc:57: Assertion failed: foo bar +[----] failmessages.cc:58: Assertion failed: foo bar +[----] failmessages.cc:59: Assertion failed: foo bar +[FAIL] messages::user: (0.00s) +[====] Synthesis: Tested: 2 | Passing: 0 | Failing: 2 | Crashing: 0  diff --git a/samples/tests/outputs/failmessages.cc.bin.out.expected b/samples/tests/outputs/failmessages.cc.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/long-messages.err.expected b/samples/tests/outputs/long-messages.c.bin.err.expected similarity index 100% rename from samples/long-messages.err.expected rename to samples/tests/outputs/long-messages.c.bin.err.expected diff --git a/samples/tests/outputs/long-messages.c.bin.out.expected b/samples/tests/outputs/long-messages.c.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/tests/outputs/long-messages.cc.bin.err.expected b/samples/tests/outputs/long-messages.cc.bin.err.expected new file mode 100644 index 00000000..fa56cf28 --- /dev/null +++ b/samples/tests/outputs/long-messages.cc.bin.err.expected @@ -0,0 +1,6 @@ +[----] long-messages.cc:4: Assertion failed: This is +[----] A long message +[----] Spawning multiple lines. +[----] Formatting is respected. +[FAIL] sample::long_msg: (0.00s) +[====] Synthesis: Tested: 1 | Passing: 0 | Failing: 1 | Crashing: 0  diff --git a/samples/tests/outputs/long-messages.cc.bin.out.expected b/samples/tests/outputs/long-messages.cc.bin.out.expected new file mode 100644 index 00000000..e69de29b diff --git a/samples/tests/pattern.sh b/samples/tests/pattern.sh index 09996f13..c1b10a9f 100755 --- a/samples/tests/pattern.sh +++ b/samples/tests/pattern.sh @@ -1,10 +1,10 @@ #!/bin/sh -e -./simple --pattern '*/passing' -./simple --pattern '!(*/passing)' -./simple --pattern '[pf]a@(ss|il)ing' -./simple --pattern '@(+(nest)ed))' -./simple --pattern '?(*(a|b))' -! ./simple --pattern '?(malformed' -./simple --pattern '[!azerty]assing' -./simple --pattern '|pipe' -./simple --pattern '\!(escaped' +./simple.c.bin --pattern '*/passing' +./simple.c.bin --pattern '!(*/passing)' +./simple.c.bin --pattern '[pf]a@(ss|il)ing' +./simple.c.bin --pattern '@(+(nest)ed))' +./simple.c.bin --pattern '?(*(a|b))' +! ./simple.c.bin --pattern '?(malformed' +./simple.c.bin --pattern '[!azerty]assing' +./simple.c.bin --pattern '|pipe' +./simple.c.bin --pattern '\!(escaped' diff --git a/samples/tests/tap_test.sh b/samples/tests/tap_test.sh index 38c0ac30..5e81fbf6 100755 --- a/samples/tests/tap_test.sh +++ b/samples/tests/tap_test.sh @@ -1,7 +1,7 @@ #!/bin/sh -./simple --tap --always-succeed -./signal --tap --always-succeed -./asserts --tap --always-succeed -./more-suites --tap --always-succeed -./long-messages --tap --always-succeed -./description --tap --always-succeed +./simple.c.bin --tap --always-succeed +./signal.c.bin --tap --always-succeed +./asserts.c.bin --tap --always-succeed +./more-suites.c.bin --tap --always-succeed +./long-messages.c.bin --tap --always-succeed +./description.c.bin --tap --always-succeed diff --git a/samples/tests/verbose.sh b/samples/tests/verbose.sh index 73410b8c..6bb1ef28 100755 --- a/samples/tests/verbose.sh +++ b/samples/tests/verbose.sh @@ -1,2 +1,2 @@ #!/bin/sh -./simple --verbose --always-succeed +./simple.c.bin --verbose --always-succeed diff --git a/samples/with-time.c b/samples/tests/with-time.c similarity index 100% rename from samples/with-time.c rename to samples/tests/with-time.c diff --git a/samples/theories.c b/samples/theories.c new file mode 100644 index 00000000..0bbe7cc2 --- /dev/null +++ b/samples/theories.c @@ -0,0 +1,121 @@ +#ifdef _MSC_VER +#pragma warning(disable : 4090) +#endif + +#include +#include + +# define INT_DATAPOINTS DataPoints(int, 0, 1, 2, -1, -2, INT_MAX, INT_MIN) + +// Let's test the multiplicative properties of 32-bit integers: + +int bad_mul(int a, int b) { + return a * b; +} + +int bad_div(int a, int b) { + return a / b; +} + +TheoryDataPoints(algebra, bad_divide_is_inverse_of_multiply) = { + INT_DATAPOINTS, + INT_DATAPOINTS, +}; + +Theory((int a, int b), algebra, bad_divide_is_inverse_of_multiply) { + cr_assume(b != 0); + cr_assert_eq(a, bad_div(bad_mul(a, b), b)); +} + +// The above implementation of mul & div fails the test because of overflows, +// let's try again: + +long long good_mul(long long a, long long b) { + return a * b; +} + +long long good_div(long long a, long long b) { + return a / b; +} + +TheoryDataPoints(algebra, good_divide_is_inverse_of_multiply) = { + INT_DATAPOINTS, + INT_DATAPOINTS, +}; + +Theory((int a, int b), algebra, good_divide_is_inverse_of_multiply) { + cr_assume(b != 0); + cr_assert_eq(a, good_div(good_mul(a, b), b)); +} + +// For triangulation + +Test(algebra, multiplication_by_integer) { + cr_assert_eq(10, good_mul(5, 2)); +} + +// Another property test + +TheoryDataPoints(algebra, zero_is_absorbing) = { + INT_DATAPOINTS, + INT_DATAPOINTS, +}; + +Theory((int a, int b), algebra, zero_is_absorbing) { + cr_assume(a == 0 || b == 0); + cr_assert_eq(0, good_mul(a, b)); +} + +// Testing for various parameters + +struct my_object { + int foo; +}; + +TheoryDataPoints(theory, misc) = { + DataPoints(char, 'a'), + DataPoints(bool, true), + DataPoints(short, 1), + DataPoints(int, 1), + DataPoints(long, 1), + DataPoints(long long, 1), + DataPoints(float, 3.14f), + DataPoints(double, 3.14), + DataPoints(char *, "test"), + DataPoints(const char *, "other test"), + DataPoints(struct my_object *, &(struct my_object) {42}), +}; + +Theory((char c, bool b, short s, int i, long l, long long ll, float f, double d, char *str, const char *cstr, struct my_object *obj), theory, misc) { + cr_assert(b); + cr_assert_eq(c, 'a'); + cr_assert_eq(s, 1); + cr_assert_eq(i, 1); + cr_assert_eq(l, 1); + cr_assert_eq(ll, 1); + cr_assert_eq(f, 3.14f); + cr_assert_eq(d, 3.14); + cr_assert_str_eq(str, "test"); + cr_assert_str_eq(cstr, "other test"); + cr_assert_eq(obj->foo, 42); + + // abort to see the formatted string of all parameters + cr_assert_fail(); +} + +// Manually generate datapoints + +TheoryDataPoints(theory, gen) = { + DataPoints(int, 0), // placeholder +}; + +static void generate_datapoints(void) { + static int arr[] = {1, 2, 3, 4, 5}; + TheoryDataPoint(theory, gen)[0].len = 5; + TheoryDataPoint(theory, gen)[0].arr = &arr; +} + +Theory((int i), theory, gen, .init = generate_datapoints) { + (void) i; + cr_assert_fail(); // we fail to display the parameter +} diff --git a/samples/theories.cc b/samples/theories.cc new file mode 100644 index 00000000..56027542 --- /dev/null +++ b/samples/theories.cc @@ -0,0 +1,129 @@ +#ifdef _MSC_VER +#pragma warning(disable : 4090) +#endif + +#include +#ifdef __cplusplus +# include +#else +# include +#endif + +# define INT_DATAPOINTS DataPoints(int, 0, 1, 2, -1, -2, INT_MAX, INT_MIN) + +// Let's test the multiplicative properties of 32-bit integers: + +int bad_mul(int a, int b) { + return a * b; +} + +int bad_div(int a, int b) { + return a / b; +} + +TheoryDataPoints(algebra, bad_divide_is_inverse_of_multiply) = { + INT_DATAPOINTS, + INT_DATAPOINTS, +}; + +Theory((int a, int b), algebra, bad_divide_is_inverse_of_multiply) { + cr_assume(b != 0); + cr_assert_eq(a, bad_div(bad_mul(a, b), b)); +} + +// The above implementation of mul & div fails the test because of overflows, +// let's try again: + +long long good_mul(long long a, long long b) { + return a * b; +} + +long long good_div(long long a, long long b) { + return a / b; +} + +TheoryDataPoints(algebra, good_divide_is_inverse_of_multiply) = { + INT_DATAPOINTS, + INT_DATAPOINTS, +}; + +Theory((int a, int b), algebra, good_divide_is_inverse_of_multiply) { + cr_assume(b != 0); + cr_assert_eq(a, good_div(good_mul(a, b), b)); +} + +// For triangulation + +Test(algebra, multiplication_by_integer) { + cr_assert_eq(10, good_mul(5, 2)); +} + +// Another property test + +TheoryDataPoints(algebra, zero_is_absorbing) = { + INT_DATAPOINTS, + INT_DATAPOINTS, +}; + +Theory((int a, int b), algebra, zero_is_absorbing) { + cr_assume(a == 0 || b == 0); + cr_assert_eq(0, good_mul(a, b)); +} + +// Testing for various parameters + +struct my_object { + int foo; +}; + +struct my_object o = {42}; + +char test_str[] = {'t', 'e', 's', 't', '\0'}; + +TheoryDataPoints(theory, misc) = { + DataPoints(char, 'a'), + DataPoints(bool, true), + DataPoints(short, 1), + DataPoints(int, 1), + DataPoints(long, 1), + DataPoints(long long, 1), + DataPoints(float, 3.14f), + DataPoints(double, 3.14), + DataPoints(char *, test_str), + DataPoints(const char *, "other test"), + DataPoints(struct my_object *, &o), +}; + +Theory((char c, bool b, short s, int i, long l, long long ll, float f, double d, char *str, const char *cstr, struct my_object *obj), theory, misc) { + cr_assert(b); + cr_assert_eq(c, 'a'); + cr_assert_eq(s, 1); + cr_assert_eq(i, 1); + cr_assert_eq(l, 1); + cr_assert_eq(ll, 1); + cr_assert_eq(f, 3.14f); + cr_assert_eq(d, 3.14); + cr_assert_str_eq(str, "test"); + cr_assert_str_eq(cstr, "other test"); + cr_assert_eq(obj->foo, 42); + + // abort to see the formatted string of all parameters + cr_assert_fail(); +} + +// Manually generate datapoints + +TheoryDataPoints(theory, gen) = { + DataPoints(int, 0), // placeholder +}; + +static void generate_datapoints(void) { + static int arr[] = {1, 2, 3, 4, 5}; + TheoryDataPoint(theory, gen)[0].len = 5; + TheoryDataPoint(theory, gen)[0].arr = &arr; +} + +Theory((int i), theory, gen, .init = generate_datapoints) { + (void) i; + cr_assert_fail(); // we fail to display the parameter +} diff --git a/samples/timeout.c b/samples/timeout.c new file mode 100644 index 00000000..db37bf04 --- /dev/null +++ b/samples/timeout.c @@ -0,0 +1,12 @@ +#include + +#ifdef _WIN32 +# include +# define sleep(x) Sleep(x * 1000) +#else +# include +#endif + +Test(timeout, simple, .timeout = 1.) { + sleep(10); +} diff --git a/src/abort.c b/src/abort.c index ea61bb98..596d9d84 100644 --- a/src/abort.c +++ b/src/abort.c @@ -21,10 +21,9 @@ * 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; +jmp_buf g_pre_test; int setup_abort_test(void) { return !setjmp(g_pre_test); diff --git a/src/abort.h b/src/abort.h index 4d85ba3f..9eab3d95 100644 --- a/src/abort.h +++ b/src/abort.h @@ -25,6 +25,9 @@ # define ABORT_H_ # include +# include + +extern jmp_buf g_pre_test; int setup_abort_test(void); diff --git a/src/asprintf.c b/src/asprintf.c new file mode 100644 index 00000000..c2a4a126 --- /dev/null +++ b/src/asprintf.c @@ -0,0 +1,63 @@ +/* + * 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 +#include +#include "criterion/asprintf-compat.h" + +int cr_asprintf(char **strp, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int res = cr_vasprintf(strp, fmt, ap); + va_end(ap); + return res; +} + +int cr_vasprintf(char **strp, const char *fmt, va_list ap) { + va_list vl; + va_copy(vl, ap); + + int size = vsnprintf(0, 0, fmt, vl); + int res = -1; + + if (size < 0 || size >= INT_MAX) { + goto cleanup; + } + + char *str = malloc(size + 1); + if (str) { + int res2 = vsnprintf(str, size + 1, fmt, ap); + if (res2 < 0 || res2 > size) { + free(str); + goto cleanup; + } + *strp = str; + res = res2; + } + +cleanup: + va_end(vl); + return res; +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 00000000..b9add0be --- /dev/null +++ b/src/common.h @@ -0,0 +1,37 @@ +/* + * 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 COMMON_H_ +# define COMMON_H_ + +#ifdef __GNUC__ +# define INLINE __attribute__((always_inline)) inline +#elif defined(_MSC_VER) +# define INLINE __forceinline +#else +# define INLINE +#endif + +# define DEF(X, Y) ((X) ? (X) : (Y)) + +#endif /* !COMMON_H_ */ diff --git a/src/entry.c b/src/entry.c new file mode 100644 index 00000000..71e9b9d0 --- /dev/null +++ b/src/entry.c @@ -0,0 +1,14 @@ +#include "criterion/criterion.h" + +CR_API int main(int argc, char *argv[]) { + struct criterion_test_set *tests = criterion_initialize(); + + if (!criterion_handle_args(argc, argv, true)) + return 0; + + int result = !criterion_run_all_tests(tests); + + criterion_finalize(tests); + + return result; +} diff --git a/src/event.c b/src/event.c index 6146c958..abb85a6c 100644 --- a/src/event.c +++ b/src/event.c @@ -23,7 +23,8 @@ */ #include -#include +#include +#include #include "criterion/stats.h" #include "criterion/common.h" #include "criterion/hooks.h" @@ -36,6 +37,12 @@ void destroy_event(void *ptr, UNUSED void *meta) { free(ev->data); } +void destroy_assert_event(void *ptr, UNUSED void *meta) { + struct event *ev = ptr; + free((void*) ((struct criterion_assert_stats *) ev->data)->message); + free(ev->data); +} + struct event *read_event(FILE *f) { unsigned kind; if (fread(&kind, sizeof (unsigned), 1, f) == 0) @@ -44,15 +51,52 @@ struct event *read_event(FILE *f) { switch (kind) { case ASSERT: { const size_t assert_size = sizeof (struct criterion_assert_stats); - unsigned char *buf = malloc(assert_size); - if (fread(buf, assert_size, 1, f) == 0) { + struct criterion_assert_stats *buf = NULL; + char *msg = NULL; + + buf = malloc(assert_size); + if (fread(buf, assert_size, 1, f) == 0) + goto fail_assert; + + size_t len = 0; + if (fread(&len, sizeof (size_t), 1, f) == 0) + goto fail_assert; + + msg = malloc(len); + if (fread(msg, len, 1, f) == 0) + goto fail_assert; + + buf->message = msg; + + struct event *ev = smalloc( + .size = sizeof (struct event), + .dtor = destroy_assert_event + ); + *ev = (struct event) { .kind = kind, .data = buf }; + return ev; + +fail_assert: + free(buf); + free(msg); + return NULL; + } + case THEORY_FAIL: { + size_t len = 0; + if (fread(&len, sizeof (size_t), 1, f) == 0) + return NULL; + + char *buf = malloc(len); + if (fread(buf, len, 1, f) == 0) { free(buf); return NULL; } - return unique_ptr(struct event, - .value = { .kind = kind, .data = buf }, - .dtor = destroy_event); + struct event *ev = smalloc( + .size = sizeof (struct event), + .dtor = destroy_event + ); + *ev = (struct event) { .kind = kind, .data = buf }; + return ev; } case POST_TEST: { double *elapsed_time = malloc(sizeof (double)); @@ -61,19 +105,26 @@ struct event *read_event(FILE *f) { return NULL; } - return unique_ptr(struct event, - .value = { .kind = kind, .data = elapsed_time }, - .dtor = destroy_event); + struct event *ev = smalloc( + .size = sizeof (struct event), + .dtor = destroy_event + ); + *ev = (struct event) { .kind = kind, .data = elapsed_time }; + return ev; + } + default: { + struct event *ev = smalloc(sizeof (struct event)); + *ev = (struct event) { .kind = kind, .data = NULL }; + return ev; } - default: - return unique_ptr(struct event, { .kind = kind, .data = NULL }); } } void send_event(int kind, void *data, size_t size) { - unsigned char buf[sizeof (int) + size]; + unsigned char *buf = malloc(sizeof (int) + size); memcpy(buf, &kind, sizeof (int)); memcpy(buf + sizeof (int), data, size); if (fwrite(buf, sizeof (int) + size, 1, g_event_pipe) == 0) abort(); + free(buf); } diff --git a/src/event.h b/src/event.h index fc8ce4fb..21d9b210 100644 --- a/src/event.h +++ b/src/event.h @@ -25,6 +25,9 @@ # define EVENT_H_ # include "criterion/event.h" +# include + +extern FILE *g_event_pipe; struct event { int kind; diff --git a/src/extmatch.c b/src/extmatch.c index 604d4183..e6c75fc0 100644 --- a/src/extmatch.c +++ b/src/extmatch.c @@ -28,6 +28,7 @@ #include #include "criterion/common.h" +#include "common.h" struct context { int depth; @@ -107,20 +108,20 @@ static int is_eos(struct context *ctx) { static inline void handle_special(struct context *ctx, handler_arg strs[5]) { if (peek_char(ctx) == '(') { - if ((strs[0].validator ?: inactive)(ctx)) + if (DEF(strs[0].validator, inactive)(ctx)) copy_str(ctx, strs[0].str); dup_char(ctx); - if ((strs[1].validator ?: inactive)(ctx)) + if (DEF(strs[1].validator, inactive)(ctx)) copy_str(ctx, strs[1].str); transform_rec(ctx); - if ((strs[2].validator ?: inactive)(ctx)) + if (DEF(strs[2].validator, inactive)(ctx)) copy_str(ctx,strs[2].str); copy_char(ctx, ')'); - if ((strs[3].validator ?: inactive)(ctx)) + if (DEF(strs[3].validator, inactive)(ctx)) copy_str(ctx, strs[3].str); - } else if ((strs[4].validator ?: inactive)(ctx)) { + } else if (DEF(strs[4].validator, inactive)(ctx)) { copy_str(ctx, strs[4].str); } } @@ -191,7 +192,7 @@ void transform_impl(struct context *ctx) { if (c == ')' && ctx->depth > 0) return; - (handler ?: copy_char)(ctx, c); + (handler ? handler : copy_char)(ctx, c); if (ctx->eos) return; @@ -247,21 +248,21 @@ static int transform(const char *pattern, char *result, const char **errmsg) { * We must now find the maximal length L such as ∀s, L >= length(T(s)) * * It is immediately apparent that the largest string will depend on the number - * of occurrences of '!()'. Hence, let u be a string that is a repeating - * sequence of '!()' padded by '.' to a multiple of 3, + * of occurrences of '!()'. Hence, ∀s, let u(s) be a string that is a repeating + * sequence of '!()' padded by at most two '.', such as length(u(s)) == length(s), * - * let N = floor(length(u) / 3), - * let Q = length(u) mod 3, + * let N = floor(length(u(s)) / 3), + * let Q = length(u(s)) mod 3, * hence num('!()') = N. * - * ∀s | lenght(s) = length(u), - * length(T(s)) <= length(T(u)) - * <= length(u) | the original length + * ∀s | lenght(s) = length(u(s)), + * length(T(s)) <= length(T(u(s))) + * <= length(u(s)) | the original length * + 4 * N | the expansion of all '!()' * + Q * diff('.') | the expansion of Q '.' * <= 3 * N + Q + 4 * N + Q * <= 7 * N + 4 - * <= 7 * floor(length(u) / 3) + 4 + * <= 7 * floor(length(u(s)) / 3) + 4 * <= 7 * floor(length(s) / 3) + 4 * */ @@ -270,7 +271,7 @@ static inline size_t max_length(size_t len) { } int extmatch(const char *pattern, const char *string, const char **errmsg) { - char regex[max_length(strlen(pattern))]; + char regex[max_length(strlen(pattern)) + 1]; if (transform(pattern, regex, errmsg) != -1) { int erroffset; pcre *preg = pcre_compile(regex, 0, errmsg, &erroffset, NULL); diff --git a/src/file.c b/src/file.c new file mode 100644 index 00000000..e48e183f --- /dev/null +++ b/src/file.c @@ -0,0 +1,53 @@ +#include + +int cr_file_match_str(FILE* f, const char *str) { + size_t len = strlen(str); + + char buf[512]; + size_t read; + int matches = 0; + while ((read = fread(buf, 1, sizeof (buf), f)) > 0) { + matches = !strncmp(buf, str, read); + if (!matches || read > len) { + matches = 0; + break; + } + + len -= read; + str += read; + } + + // consume the rest of what's available + while (fread(buf, 1, sizeof (buf), f) > 0); + + return matches; +} + +int cr_file_match_file(FILE* f, FILE* ref) { + char buf1[512]; + char buf2[512]; + + fpos_t orig_pos; + fgetpos(ref, &orig_pos); + rewind(ref); + + size_t read1 = 1, read2 = 1; + int matches = 0; + while ((read1 = fread(buf1, 1, sizeof (buf1), f)) > 0 + && (read2 = fread(buf2, 1, sizeof (buf2), ref)) > 0) { + + if (read1 != read2) { + matches = 0; + break; + } + + matches = !memcmp(buf1, buf2, read1); + } + + // consume the rest of what's available + while (fread(buf1, 1, sizeof (buf1), f) > 0); + + fsetpos(ref, &orig_pos); + + return matches; +} diff --git a/src/i18n.c b/src/i18n.c index 5b9899b4..c83a119c 100644 --- a/src/i18n.c +++ b/src/i18n.c @@ -1,8 +1,44 @@ #include "i18n.h" +#include "criterion/assert.h" +#include "criterion/asprintf-compat.h" -#if ENABLE_NLS -__attribute__ ((constructor)) void init_i18n(void) { +#if ENABLE_NLS bindtextdomain (PACKAGE, LOCALEDIR); +#endif } + +char *translate_assert_msg(int msg_index, ...) { + static char *messages[] = { + [CRITERION_ASSERT_MSG_FAIL] = N_("The conditions for this assertion were not met."), + [CRITERION_ASSERT_MSG_EXPR_FALSE] = N_("The expression %s is false."), + [CRITERION_ASSERT_MSG_EXPR_AS_STRINGS_FALSE] = N_("The expression (as strings) %s is false."), + [CRITERION_ASSERT_MSG_IS_NULL] = N_("%s is null."), + [CRITERION_ASSERT_MSG_IS_NOT_NULL] = N_("%s is not null."), + [CRITERION_ASSERT_MSG_IS_EMPTY] = N_("%s is empty."), + [CRITERION_ASSERT_MSG_IS_NOT_EMPTY] = N_("%s is not empty."), + [CRITERION_ASSERT_MSG_ANY_THROW] = N_("The statement `%s` did not throw any exception."), + [CRITERION_ASSERT_MSG_NONE_THROW] = N_("The statement `%s` threw some exception."), + +#ifdef ENABLE_NLS + [CRITERION_ASSERT_MSG_FILE_STR_MATCH] = N_("The file contents of %1$s does not match the string \"%2$s\"."), + [CRITERION_ASSERT_MSG_FILE_MATCH] = N_("The file contents of %1$s does not match the contents of %2$s."), + [CRITERION_ASSERT_MSG_THROW] = N_("The statement `%1$s` did throw an instance of the `%2$s` exception."), + [CRITERION_ASSERT_MSG_NO_THROW] = N_("The statement `%1$s` did not throw an instance of the `%2$s` exception."), +#else + [CRITERION_ASSERT_MSG_FILE_STR_MATCH] = "The file contents of %s does not match the string \"%s\".", + [CRITERION_ASSERT_MSG_FILE_MATCH] = "The file contents of %s does not match the contents of %s.", + [CRITERION_ASSERT_MSG_THROW] = "The statement `%s` did throw an instance of the `%s` exception.", + [CRITERION_ASSERT_MSG_NO_THROW] = "The statement `%s` did not throw an instance of the `%s` exception.", #endif + + }; + + va_list vl; + va_start(vl, msg_index); + char *out; + cr_vasprintf(&out, _(messages[msg_index]), vl); + va_end(vl); + + return out; +} diff --git a/src/i18n.h b/src/i18n.h index 74c159ec..827dc0a2 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -13,4 +13,10 @@ dngettext(PACKAGE, String, Plural, (Quantity)) # endif +// Used to mark string for gettext +# define N_(Str) Str +# define N_s(Str, Pl) {Str, Pl} + +void init_i18n(void); + #endif /* !I18N_H_ */ diff --git a/src/log/normal.c b/src/log/normal.c index 565e763b..6e3b1dbd 100644 --- a/src/log/normal.c +++ b/src/log/normal.c @@ -34,19 +34,18 @@ #include "config.h" #include "i18n.h" #include "posix-compat.h" - -#define USED __attribute__ ((used)) +#include "common.h" #ifdef VANILLA_WIN32 // fallback to strtok on windows since strtok_s is not available everywhere # define strtok_r(str, delim, saveptr) strtok(str, delim) #endif -typedef const char *const msg_t; +#ifdef _MSC_VER +# define strdup _strdup +#endif -// Used to mark string for gettext -# define N_(Str) Str -# define N_s(Str, Pl) {Str, Pl} +typedef const char *const msg_t; static msg_t msg_pre_all = N_("Criterion v%s\n"); static msg_t msg_desc = N_(" %s\n"); @@ -58,9 +57,12 @@ static msg_t msg_post_test = N_("%1$s::%2$s\n"); static msg_t msg_post_suite_test = N_("%1$s::%2$s: Test is disabled\n"); static msg_t msg_post_suite_suite = N_("%1$s::%2$s: Suite is disabled\n"); static msg_t msg_assert_fail = N_("%1$s%2$s%3$s:%4$s%5$d%6$s: Assertion failed: %7$s\n"); +static msg_t msg_theory_fail = N_(" Theory %1$s::%2$s failed with the following parameters: (%3$s)\n"); +static msg_t msg_test_timeout = N_("%1$s::%2$s: Timed out. (%3$3.2fs)\n"); static msg_t msg_test_crash_line = N_("%1$s%2$s%3$s:%4$s%5$u%6$s: Unexpected signal caught below this line!\n"); static msg_t msg_test_crash = N_("%1$s::%2$s: CRASH!\n"); static msg_t msg_test_other_crash = N_("%1$sWarning! The test `%2$s::%3$s` crashed during its setup or teardown.%4$s\n"); +static msg_t msg_test_abnormal_exit = N_("%1$sWarning! The test `%2$s::%3$s` exited during its setup or teardown.%4$s\n"); static msg_t msg_pre_suite[] = N_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"); static msg_t msg_post_all = N_("%1$sSynthesis: Tested: %2$s%3$lu%4$s " @@ -75,9 +77,12 @@ static msg_t msg_post_test = "%s::%s\n"; static msg_t msg_post_suite_test = "%s::%s: Test is disabled\n"; static msg_t msg_post_suite_suite = "%s::%s: Suite is disabled\n"; static msg_t msg_assert_fail = "%s%s%s:%s%d%s: Assertion failed: %s\n"; +static msg_t msg_theory_fail = " Theory %s::%s failed with the following parameters: %s\n"; +static msg_t msg_test_timeout = "%s::%s: Timed out. (%3.2fs)\n"; static msg_t msg_test_crash_line = "%s%s%s:%s%u%s: Unexpected signal caught below this line!\n"; static msg_t msg_test_crash = "%s::%s: CRASH!\n"; static msg_t msg_test_other_crash = "%sWarning! The test `%s::%s` crashed during its setup or teardown.%s\n"; +static msg_t msg_test_abnormal_exit = "%sWarning! The test `%s::%s` exited during its setup or teardown.%s\n"; static msg_t msg_pre_suite[] = { "Running %s%lu%s test from %s%s%s:\n", "Running %s%lu%s tests from %s%s%s:\n" }; static msg_t msg_post_all = "%sSynthesis: Tested: %s%lu%s " @@ -115,8 +120,7 @@ void normal_log_post_test(struct criterion_test_stats *stats) { stats->elapsed_time); } -__attribute__((always_inline)) -static inline bool is_disabled(struct criterion_test *t, +static INLINE bool is_disabled(struct criterion_test *t, struct criterion_suite *s) { return t->data->disabled || (s->data && s->data->disabled); } @@ -154,8 +158,7 @@ void normal_log_post_all(struct criterion_global_stats *stats) { void normal_log_assert(struct criterion_assert_stats *stats) { if (!stats->passed) { - char *dup = strdup(*stats->message ? stats->message - : stats->condition); + char *dup = strdup(*stats->message ? stats->message : ""); #ifdef VANILLA_WIN32 char *line = strtok(dup, "\n"); @@ -198,6 +201,12 @@ void normal_log_other_crash(UNUSED struct criterion_test_stats *stats) { FG_BOLD, stats->test->category, stats->test->name, RESET); } +void normal_log_abnormal_exit(UNUSED struct criterion_test_stats *stats) { + criterion_pimportant(CRITERION_PREFIX_DASHES, + _(msg_test_abnormal_exit), + FG_BOLD, stats->test->category, stats->test->name, RESET); +} + void normal_log_pre_suite(struct criterion_suite_set *set) { criterion_pinfo(CRITERION_PREFIX_EQUALS, _s(msg_pre_suite[0], msg_pre_suite[1], set->tests->size), @@ -205,13 +214,32 @@ void normal_log_pre_suite(struct criterion_suite_set *set) { FG_GOLD, set->suite.name, RESET); } +void normal_log_theory_fail(struct criterion_theory_stats *stats) { + criterion_pimportant(CRITERION_PREFIX_DASHES, + _(msg_theory_fail), + stats->stats->test->category, + stats->stats->test->name, + stats->formatted_args); +} + +void normal_log_test_timeout(UNUSED struct criterion_test_stats *stats) { + criterion_pimportant(CRITERION_PREFIX_FAIL, + _(msg_test_timeout), + stats->test->category, + stats->test->name, + stats->elapsed_time); +} + 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_theory_fail = normal_log_theory_fail, + .log_test_timeout = normal_log_test_timeout, .log_test_crash = normal_log_test_crash, .log_other_crash = normal_log_other_crash, + .log_abnormal_exit = normal_log_abnormal_exit, .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/log/tap.c b/src/log/tap.c index 23898f46..9a87b78d 100644 --- a/src/log/tap.c +++ b/src/log/tap.c @@ -32,6 +32,11 @@ #include "timer.h" #include "config.h" #include "posix-compat.h" +#include "common.h" + +#ifdef _MSC_VER +# define strdup _strdup +#endif void tap_log_pre_all(struct criterion_test_set *set) { size_t enabled_count = 0; @@ -54,8 +59,7 @@ void tap_log_pre_suite(struct criterion_suite_set *set) { set->suite.name); } -__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); } @@ -65,7 +69,7 @@ void tap_log_post_suite(struct criterion_suite_stats *stats) { criterion_important("ok - %s::%s %s # SKIP %s is disabled\n", ts->test->category, ts->test->name, - ts->test->data->description ?: "", + DEF(ts->test->data->description, ""), ts->test->data->disabled ? "test" : "suite"); } } @@ -78,11 +82,11 @@ void tap_log_post_test(struct criterion_test_stats *stats) { stats->failed ? "not ok" : "ok", stats->test->category, stats->test->name, - stats->test->data->description ?: "", + DEF(stats->test->data->description, ""), stats->elapsed_time); for (struct criterion_assert_stats *asrt = stats->asserts; asrt; asrt = asrt->next) { if (!asrt->passed) { - char *dup = strdup(*asrt->message ? asrt->message : asrt->condition); + char *dup = strdup(*asrt->message ? asrt->message : ""); #ifdef VANILLA_WIN32 char *line = strtok(dup, "\n"); #else @@ -114,10 +118,18 @@ void tap_log_test_crash(struct criterion_test_stats *stats) { stats->progress); } +void tap_log_test_timeout(struct criterion_test_stats *stats) { + criterion_important("not ok - %s::%s timed out (%3.2fs)\n", + stats->test->category, + stats->test->name, + stats->elapsed_time); +} + struct criterion_output_provider tap_logging = { - .log_pre_all = tap_log_pre_all, - .log_pre_suite = tap_log_pre_suite, - .log_test_crash = tap_log_test_crash, - .log_post_test = tap_log_post_test, - .log_post_suite = tap_log_post_suite, + .log_pre_all = tap_log_pre_all, + .log_pre_suite = tap_log_pre_suite, + .log_test_crash = tap_log_test_crash, + .log_test_timeout = tap_log_test_timeout, + .log_post_test = tap_log_post_test, + .log_post_suite = tap_log_post_suite, }; diff --git a/src/main.c b/src/main.c index 1faa4a38..3aa95841 100644 --- a/src/main.c +++ b/src/main.c @@ -22,15 +22,16 @@ * THE SOFTWARE. */ #define _GNU_SOURCE -#include -#include -#include #include #include #include -#include +#include +#include "criterion/criterion.h" +#include "criterion/options.h" +#include "criterion/ordered-set.h" #include "runner.h" #include "config.h" +#include "common.h" #if ENABLE_NLS # include @@ -90,7 +91,7 @@ bool is_disabled(struct criterion_suite *s, struct criterion_test *t) { } int list_tests(bool unicode) { - smart struct criterion_test_set *set = criterion_init(); + struct criterion_test_set *set = criterion_init(); const char *node = unicode ? UTF8_TREE_NODE : ASCII_TREE_NODE; const char *join = unicode ? UTF8_TREE_JOIN : ASCII_TREE_JOIN; @@ -114,10 +115,12 @@ int list_tests(bool unicode) { is_disabled(&s->suite, t) ? " (disabled)" : ""); } } + + sfree(set); return 0; } -int main(int argc, char *argv[]) { +int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) { static struct option opts[] = { {"verbose", optional_argument, 0, 'b'}, {"version", no_argument, 0, 'v'}, @@ -135,8 +138,8 @@ int main(int argc, char *argv[]) { {0, 0, 0, 0 } }; - bool use_ascii = !strcmp("1", getenv("CRITERION_USE_ASCII") ?: "0") - || !strcmp("dumb", getenv("TERM") ?: "dumb"); + bool use_ascii = !strcmp("1", DEF(getenv("CRITERION_USE_ASCII"), "0")) + || !strcmp("dumb", DEF(getenv("TERM"), "dumb")); setlocale(LC_ALL, ""); #if ENABLE_NLS @@ -144,24 +147,24 @@ int main(int argc, char *argv[]) { #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->always_succeed = !strcmp("1", DEF(getenv("CRITERION_ALWAYS_SUCCEED"), "0")); + opt->no_early_exit = !strcmp("1", DEF(getenv("CRITERION_NO_EARLY_EXIT") , "0")); + opt->fail_fast = !strcmp("1", DEF(getenv("CRITERION_FAIL_FAST") , "0")); opt->use_ascii = use_ascii; - opt->logging_threshold = atoi(getenv("CRITERION_VERBOSITY_LEVEL") ?: "2"); - opt->short_filename = !strcmp("1", getenv("CRITERION_SHORT_FILENAME") ?: "0"); + opt->logging_threshold = atoi(DEF(getenv("CRITERION_VERBOSITY_LEVEL"), "2")); + opt->short_filename = !strcmp("1", DEF(getenv("CRITERION_SHORT_FILENAME"), "0")); #ifdef HAVE_PCRE opt->pattern = getenv("CRITERION_TEST_PATTERN"); #endif - bool use_tap = !strcmp("1", getenv("CRITERION_ENABLE_TAP") ?: "0"); + bool use_tap = !strcmp("1", DEF(getenv("CRITERION_ENABLE_TAP"), "0")); bool do_list_tests = false; bool do_print_version = false; bool do_print_usage = false; for (int c; (c = getopt_long(argc, argv, "hvlfS", opts, NULL)) != -1;) { switch (c) { - case 'b': criterion_options.logging_threshold = atoi(optarg ?: "1"); break; + case 'b': criterion_options.logging_threshold = atoi(DEF(optarg, "1")); break; case 'y': criterion_options.always_succeed = true; break; case 'z': criterion_options.no_early_exit = true; break; case 'k': criterion_options.use_ascii = true; break; @@ -173,8 +176,8 @@ int main(int argc, char *argv[]) { case 't': use_tap = true; break; case 'l': do_list_tests = true; break; case 'v': do_print_version = true; break; - case 'h': - default : do_print_usage = true; break; + case 'h': do_print_usage = true; break; + default : do_print_usage = handle_unknown_arg; break; } } if (use_tap) @@ -186,5 +189,5 @@ int main(int argc, char *argv[]) { if (do_list_tests) return list_tests(!criterion_options.use_ascii); - return !criterion_run_all_tests(); + return 1; } diff --git a/src/ordered-set.c b/src/ordered-set.c index 9386b592..00630238 100644 --- a/src/ordered-set.c +++ b/src/ordered-set.c @@ -21,30 +21,36 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include #include #include -#include +#include +#include "common.h" static void destroy_ordered_set(void *ptr, UNUSED void *meta) { sfree(((struct criterion_ordered_set *) ptr)->first); } -__attribute__ ((always_inline)) -static inline void nothing() {} +static INLINE void nothing(UNUSED void *ptr, UNUSED void *meta) {} static void destroy_ordered_set_node(void *ptr, void *meta) { struct criterion_ordered_set *set = *(void **) meta; struct criterion_ordered_set_node *n = ptr; - (set->dtor ?: nothing)(n->data, NULL); + DEF(set->dtor, nothing)(n->data, NULL); sfree(((struct criterion_ordered_set_node *) ptr)->next); } struct criterion_ordered_set *new_ordered_set(f_criterion_cmp cmp, f_destructor dtor) { - return unique_ptr(struct criterion_ordered_set, - .value = { .cmp = cmp, .dtor = dtor }, - .dtor = destroy_ordered_set); + struct criterion_ordered_set *newset = smalloc( + .size = sizeof (struct criterion_ordered_set), + .dtor = destroy_ordered_set + ); + + struct criterion_ordered_set data = { .cmp = cmp, .dtor = dtor }; + memcpy(newset, &data, sizeof (struct criterion_ordered_set)); + return newset; } void *insert_ordered_set(struct criterion_ordered_set *l, diff --git a/src/posix-compat.c b/src/posix-compat.c index 376cd278..72dd6db0 100644 --- a/src/posix-compat.c +++ b/src/posix-compat.c @@ -24,6 +24,8 @@ #include #include "posix-compat.h" #include "process.h" +#include "criterion/assert.h" +#include "criterion/redirect.h" #ifdef VANILLA_WIN32 # define VC_EXTRALEAN @@ -52,9 +54,6 @@ WriteProcessMemory(Proc, &What, &What, Size, NULL); # include -# ifndef SIGALRM -# define SIGALRM 14 -# endif #else # include @@ -63,7 +62,7 @@ # include #endif -#include +#include struct proc_handle { #ifdef VANILLA_WIN32 @@ -91,7 +90,7 @@ struct full_context { struct criterion_test_extra_data suite_data; f_worker_func func; struct pipe_handle pipe; - int resumed; + volatile int resumed; }; static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker"); @@ -201,14 +200,19 @@ s_proc_handle *fork_process() { UnmapViewOfFile(ctx); CloseHandle(sharedMem); - return unique_ptr(s_proc_handle, { info.hProcess }); + s_proc_handle *handle = smalloc(sizeof (s_proc_handle)); + *handle = (s_proc_handle) { info.hProcess }; + return handle; #else pid_t pid = fork(); if (pid == -1) return (void *) -1; if (pid == 0) return NULL; - return unique_ptr(s_proc_handle, { pid }); + + s_proc_handle *handle = smalloc(sizeof (s_proc_handle)); + *handle = (s_proc_handle) { pid }; + return handle; #endif } @@ -235,7 +239,7 @@ void wait_process(s_proc_handle *handle, int *status) { case STATUS_PRIVILEGED_INSTRUCTION: case STATUS_NONCONTINUABLE_EXCEPTION: sig = SIGILL; break; - case STATUS_TIMEOUT: sig = SIGALRM; break; + case CR_EXCEPTION_TIMEOUT: sig = SIGPROF; break; case STATUS_ACCESS_VIOLATION: case STATUS_DATATYPE_MISALIGNMENT: @@ -257,15 +261,17 @@ void wait_process(s_proc_handle *handle, int *status) { #endif } -FILE *pipe_in(s_pipe_handle *p) { +FILE *pipe_in(s_pipe_handle *p, int do_close) { #ifdef VANILLA_WIN32 - CloseHandle(p->fhs[1]); + if (do_close) + CloseHandle(p->fhs[1]); int fd = _open_osfhandle((intptr_t) p->fhs[0], _O_RDONLY); if (fd == -1) return NULL; FILE *in = _fdopen(fd, "r"); #else - close(p->fds[1]); + if (do_close) + close(p->fds[1]); FILE *in = fdopen(p->fds[0], "r"); #endif if (!in) @@ -275,15 +281,17 @@ FILE *pipe_in(s_pipe_handle *p) { return in; } -FILE *pipe_out(s_pipe_handle *p) { +FILE *pipe_out(s_pipe_handle *p, int do_close) { #ifdef VANILLA_WIN32 - CloseHandle(p->fhs[0]); + if (do_close) + CloseHandle(p->fhs[0]); int fd = _open_osfhandle((intptr_t) p->fhs[1], _O_WRONLY); if (fd == -1) return NULL; FILE *out = _fdopen(fd, "w"); #else - close(p->fds[0]); + if (do_close) + close(p->fds[0]); FILE *out = fdopen(p->fds[1], "w"); #endif if (!out) @@ -293,7 +301,7 @@ FILE *pipe_out(s_pipe_handle *p) { return out; } -s_pipe_handle *stdpipe() { +int stdpipe_stack(s_pipe_handle *out) { #ifdef VANILLA_WIN32 HANDLE fhs[2]; SECURITY_ATTRIBUTES attr = { @@ -301,22 +309,32 @@ s_pipe_handle *stdpipe() { .bInheritHandle = TRUE }; if (!CreatePipe(fhs, fhs + 1, &attr, 0)) - return NULL; - return unique_ptr(s_pipe_handle, {{ fhs[0], fhs[1] }}); + return -1; + *out = (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] }}); + return -1; + *out = (s_pipe_handle) {{ fds[0], fds[1] }}; #endif + return 0; +} + +s_pipe_handle *stdpipe() { + s_pipe_handle *handle = smalloc(sizeof (s_pipe_handle)); + if (stdpipe_stack(handle) < 0) + return NULL; + return handle; } s_proc_handle *get_current_process() { + s_proc_handle *handle = smalloc(sizeof (s_proc_handle)); #ifdef VANILLA_WIN32 - return unique_ptr(s_proc_handle, { GetCurrentProcess() }); + *handle = (s_proc_handle) { GetCurrentProcess() }; #else - return unique_ptr(s_proc_handle, { getpid() }); + *handle = (s_proc_handle) { getpid() }; #endif + return handle; } bool is_current_process(s_proc_handle *proc) { @@ -395,3 +413,171 @@ const char *basename_compat(const char *str) { start = c + 1; return start; } + +#ifdef VANILLA_WIN32 +typedef DWORD cr_std_fd; +#else +typedef int cr_std_fd; +#endif + +static s_pipe_handle stdout_redir; +static s_pipe_handle stderr_redir; +static s_pipe_handle stdin_redir; + +enum criterion_std_fd { + CR_STDIN = 0, + CR_STDOUT = 1, + CR_STDERR = 2, +}; + +enum criterion_pipe_end { + PIPE_READ = 0, + PIPE_WRITE = 1, +}; + +cr_std_fd get_std_fd(int fd_kind) { + static int kinds[] = { +#ifdef VANILLA_WIN32 + [CR_STDIN] = STD_INPUT_HANDLE, + [CR_STDOUT] = STD_OUTPUT_HANDLE, + [CR_STDERR] = STD_ERROR_HANDLE, +#else + [CR_STDIN] = STDIN_FILENO, + [CR_STDOUT] = STDOUT_FILENO, + [CR_STDERR] = STDERR_FILENO, +#endif + }; + + return kinds[fd_kind]; +} + +FILE* get_std_file(int fd_kind) { + switch (fd_kind) { + case CR_STDIN: return stdin; + case CR_STDOUT: return stdout; + case CR_STDERR: return stderr; + } + return NULL; +} + +int make_redirect_pipe(s_pipe_handle *handle, int id, int noblock) { +#ifdef VANILLA_WIN32 + HANDLE fhs[2]; + SECURITY_ATTRIBUTES attr = { + .nLength = sizeof (SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE + }; + char pipe_name[256] = {0}; + snprintf(pipe_name, sizeof (pipe_name), + "\\\\.\\pipe\\criterion_%lu_%d", GetCurrentProcessId(), id); + fhs[0] = CreateNamedPipe(pipe_name, + PIPE_ACCESS_INBOUND, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE + | (noblock ? PIPE_NOWAIT : PIPE_WAIT), + 1, + 4096 * 4, + 4096 * 4, + 0, + &attr); + + if (fhs[0] == INVALID_HANDLE_VALUE) + return 0; + + fhs[1] = CreateFile(pipe_name, + GENERIC_WRITE, + 0, + &attr, + OPEN_EXISTING, + 0, + NULL); + + if (fhs[1] == INVALID_HANDLE_VALUE) { + CloseHandle(fhs[0]); + return 0; + } + + *handle = (s_pipe_handle) {{ fhs[0], fhs[1] }}; +#else + (void) id; + + int fds[2] = { -1, -1 }; + if (pipe(fds) == -1) + return 0; + + if (noblock) + for (int i = 0; i < 2; ++i) + fcntl(fds[i], F_SETFL, fcntl(fds[i], F_GETFL) | O_NONBLOCK); + + *handle = (s_pipe_handle) {{ fds[0], fds[1] }}; +#endif + return 1; +} + +void cr_redirect(int fd_kind, s_pipe_handle *pipe, int fd_index, int noblock) { + fflush(get_std_file(fd_kind)); + if (!make_redirect_pipe(pipe, fd_kind, noblock)) + cr_assert_fail("Could not redirect standard file descriptor."); + + cr_std_fd fd = get_std_fd(fd_kind); +#ifdef VANILLA_WIN32 + int stdfd = _open_osfhandle((intptr_t) pipe->fhs[fd_index], fd_kind == 0 ? _O_RDONLY : _O_WRONLY); + if (stdfd == -1) + cr_assert_fail("Could not redirect standard file descriptor."); + + fflush(get_std_file(fd_kind)); + + _close(fd_kind); + SetStdHandle(fd, pipe->fhs[fd_index]); + + _dup2(stdfd, fd_kind); + _close(stdfd); + + setvbuf(get_std_file(fd_kind), NULL, _IONBF, 0); +#else + close(fd); + dup2(pipe->fds[fd_index], fd); + close(pipe->fds[fd_index]); +#endif +} + +void cr_redirect_stdout(void) { + cr_redirect(CR_STDOUT, &stdout_redir, PIPE_WRITE, 1); +} + +void cr_redirect_stderr(void) { + cr_redirect(CR_STDERR, &stderr_redir, PIPE_WRITE, 1); +} + +void cr_redirect_stdin(void) { + cr_redirect(CR_STDIN, &stdin_redir, PIPE_READ, 0); +} + +FILE* cr_get_redirected_stdout(void) { + static FILE *f; + if (!f) { + f = pipe_in(&stdout_redir, 0); + if (!f) + cr_assert_fail("Could not get redirected stdout read end."); + } + return f; +} + +FILE* cr_get_redirected_stderr(void) { + static FILE *f; + if (!f) { + f = pipe_in(&stderr_redir, 0); + if (!f) + cr_assert_fail("Could not get redirected stderr read end."); + } + return f; +} + +FILE* cr_get_redirected_stdin(void) { + static FILE *f; + if (!f) { + f = pipe_out(&stdin_redir, 0); + if (!f) + cr_assert_fail("Could not get redirected stdin write end."); + } + return f; +} diff --git a/src/posix-compat.h b/src/posix-compat.h index 9f938dbe..e6b30058 100644 --- a/src/posix-compat.h +++ b/src/posix-compat.h @@ -43,6 +43,9 @@ # define WTERMSIG(Status) ((Status) & 0x7F) # define WIFEXITED(Status) (WTERMSIG(Status) == 0) # define WIFSIGNALED(Status) (((signed char) (WTERMSIG(Status) + 1) >> 1) > 0) + +# define SIGPROF 27 +# define CR_EXCEPTION_TIMEOUT 0xC0001042 # else # include # endif @@ -67,8 +70,8 @@ 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); +FILE *pipe_in(s_pipe_handle *p, int do_close); +FILE *pipe_out(s_pipe_handle *p, int do_close); s_proc_handle *fork_process(); void wait_process(s_proc_handle *handle, int *status); diff --git a/src/process.c b/src/process.c index d3a5f9e9..561665c2 100644 --- a/src/process.c +++ b/src/process.c @@ -23,10 +23,11 @@ */ #include #include -#include +#include #include "criterion/types.h" #include "criterion/options.h" +#include "criterion/redirect.h" #include "process.h" #include "event.h" #include "posix-compat.h" @@ -61,8 +62,8 @@ struct event *worker_read_event(struct process *proc) { } void run_worker(struct worker_context *ctx) { - fclose(stdin); - g_event_pipe = pipe_out(ctx->pipe); + cr_redirect_stdin(); + g_event_pipe = pipe_out(ctx->pipe, 1); ctx->func(ctx->test, ctx->suite); fclose(g_event_pipe); @@ -76,7 +77,7 @@ void run_worker(struct worker_context *ctx) { struct process *spawn_test_worker(struct criterion_test *test, struct criterion_suite *suite, f_worker_func func) { - smart s_pipe_handle *pipe = stdpipe(); + s_pipe_handle *pipe = stdpipe(); if (pipe == NULL) abort(); @@ -86,17 +87,26 @@ struct process *spawn_test_worker(struct criterion_test *test, .func = func, .pipe = pipe }; + + struct process *ptr = NULL; + s_proc_handle *proc = fork_process(); if (proc == (void *) -1) { - return NULL; + goto cleanup; } else if (proc == NULL) { run_worker(&g_worker_context); - return NULL; + goto cleanup; } - return unique_ptr(struct process, - .value = { .proc = proc, .in = pipe_in(pipe) }, - .dtor = close_process); + ptr = smalloc( + .size = sizeof (struct process), + .kind = UNIQUE, + .dtor = close_process); + + *ptr = (struct process) { .proc = proc, .in = pipe_in(pipe, 1) }; +cleanup: + sfree(pipe); + return ptr; } struct process_status wait_proc(struct process *proc) { diff --git a/src/report.c b/src/report.c index 64a6861f..9a6f80ec 100644 --- a/src/report.c +++ b/src/report.c @@ -41,29 +41,57 @@ static inline void nothing() {} for (f_report_hook *hook = GET_SECTION_START(HOOK_SECTION(Kind)); \ hook < (f_report_hook*) GET_SECTION_END(HOOK_SECTION(Kind)); \ ++hook) { \ - (*hook ?: nothing)(data); \ + (*hook ? *hook : nothing)(data); \ } \ } +#ifdef _MSC_VER +f_report_hook SECTION_START_(HOOK_SECTION(PRE_ALL)); +f_report_hook SECTION_START_(HOOK_SECTION(PRE_SUITE)); +f_report_hook SECTION_START_(HOOK_SECTION(PRE_INIT)); +f_report_hook SECTION_START_(HOOK_SECTION(PRE_TEST)); +f_report_hook SECTION_START_(HOOK_SECTION(ASSERT)); +f_report_hook SECTION_START_(HOOK_SECTION(THEORY_FAIL)); +f_report_hook SECTION_START_(HOOK_SECTION(TEST_CRASH)); +f_report_hook SECTION_START_(HOOK_SECTION(POST_TEST)); +f_report_hook SECTION_START_(HOOK_SECTION(POST_FINI)); +f_report_hook SECTION_START_(HOOK_SECTION(POST_SUITE)); +f_report_hook SECTION_START_(HOOK_SECTION(POST_ALL)); + +f_report_hook SECTION_END_(HOOK_SECTION(PRE_ALL)); +f_report_hook SECTION_END_(HOOK_SECTION(PRE_SUITE)); +f_report_hook SECTION_END_(HOOK_SECTION(PRE_INIT)); +f_report_hook SECTION_END_(HOOK_SECTION(PRE_TEST)); +f_report_hook SECTION_END_(HOOK_SECTION(ASSERT)); +f_report_hook SECTION_END_(HOOK_SECTION(THEORY_FAIL)); +f_report_hook SECTION_END_(HOOK_SECTION(TEST_CRASH)); +f_report_hook SECTION_END_(HOOK_SECTION(POST_TEST)); +f_report_hook SECTION_END_(HOOK_SECTION(POST_FINI)); +f_report_hook SECTION_END_(HOOK_SECTION(POST_SUITE)); +f_report_hook SECTION_END_(HOOK_SECTION(POST_ALL)); +#endif + IMPL_CALL_REPORT_HOOKS(PRE_ALL); IMPL_CALL_REPORT_HOOKS(PRE_SUITE); IMPL_CALL_REPORT_HOOKS(PRE_INIT); IMPL_CALL_REPORT_HOOKS(PRE_TEST); IMPL_CALL_REPORT_HOOKS(ASSERT); +IMPL_CALL_REPORT_HOOKS(THEORY_FAIL); IMPL_CALL_REPORT_HOOKS(TEST_CRASH); IMPL_CALL_REPORT_HOOKS(POST_TEST); IMPL_CALL_REPORT_HOOKS(POST_FINI); IMPL_CALL_REPORT_HOOKS(POST_SUITE); IMPL_CALL_REPORT_HOOKS(POST_ALL); -ReportHook(PRE_ALL)() {} -ReportHook(PRE_SUITE)() {} -ReportHook(PRE_INIT)() {} -ReportHook(PRE_TEST)() {} -ReportHook(ASSERT)() {} -ReportHook(TEST_CRASH)() {} -ReportHook(POST_TEST)() {} -ReportHook(POST_FINI)() {} -ReportHook(POST_SUITE)() {} -ReportHook(POST_ALL)() {} +ReportHook(PRE_ALL)(UNUSED struct criterion_test_set *arg) {} +ReportHook(PRE_SUITE)(UNUSED struct criterion_suite_set *arg) {} +ReportHook(PRE_INIT)(UNUSED struct criterion_test *arg) {} +ReportHook(PRE_TEST)(UNUSED struct criterion_test *arg) {} +ReportHook(ASSERT)(UNUSED struct criterion_assert_stats *arg) {} +ReportHook(THEORY_FAIL)(UNUSED struct criterion_theory_stats *arg) {} +ReportHook(TEST_CRASH)(UNUSED struct criterion_test_stats *arg) {} +ReportHook(POST_TEST)(UNUSED struct criterion_test_stats *arg) {} +ReportHook(POST_FINI)(UNUSED struct criterion_test_stats *arg) {} +ReportHook(POST_SUITE)(UNUSED struct criterion_suite_stats *arg) {} +ReportHook(POST_ALL)(UNUSED struct criterion_global_stats *arg) {} diff --git a/src/report.h b/src/report.h index aab2a103..bc2d166e 100644 --- a/src/report.h +++ b/src/report.h @@ -37,6 +37,7 @@ DECL_CALL_REPORT_HOOKS(PRE_SUITE); DECL_CALL_REPORT_HOOKS(PRE_INIT); DECL_CALL_REPORT_HOOKS(PRE_TEST); DECL_CALL_REPORT_HOOKS(ASSERT); +DECL_CALL_REPORT_HOOKS(THEORY_FAIL); DECL_CALL_REPORT_HOOKS(TEST_CRASH); DECL_CALL_REPORT_HOOKS(POST_TEST); DECL_CALL_REPORT_HOOKS(POST_FINI); @@ -44,6 +45,8 @@ DECL_CALL_REPORT_HOOKS(POST_SUITE); DECL_CALL_REPORT_HOOKS(POST_ALL); #define log(Type, Arg) \ - (criterion_options.output_provider->log_ ## Type ?: nothing)(Arg); + log_(criterion_options.output_provider->log_ ## Type, Arg); +#define log_(Log, Arg) \ + (Log ? Log(Arg) : nothing()); #endif /* !REPORT_H_ */ diff --git a/src/runner.c b/src/runner.c index c28da672..c752d86a 100644 --- a/src/runner.c +++ b/src/runner.c @@ -23,7 +23,7 @@ */ #include #include -#include +#include #include "criterion/criterion.h" #include "criterion/options.h" #include "criterion/ordered-set.h" @@ -37,11 +37,20 @@ #include "posix-compat.h" #include "abort.h" #include "config.h" +#include "i18n.h" +#include "common.h" #ifdef HAVE_PCRE #include "extmatch.h" #endif +#ifdef _MSC_VER +struct criterion_test SECTION_START_(cr_tst); +struct criterion_suite SECTION_START_(cr_sts); +struct criterion_test SECTION_END_(cr_tst); +struct criterion_suite SECTION_END_(cr_sts); +#endif + IMPL_SECTION_LIMITS(struct criterion_test, cr_tst); IMPL_SECTION_LIMITS(struct criterion_suite, cr_sts); @@ -49,8 +58,7 @@ IMPL_SECTION_LIMITS(struct criterion_suite, cr_sts); TestSuite(); Test(,) {}; -__attribute__ ((always_inline)) -static inline void nothing() {} +static INLINE void nothing(void) {} int cmp_suite(void *a, void *b) { struct criterion_suite *s1 = a, *s2 = b; @@ -72,6 +80,20 @@ static void dtor_test_set(void *ptr, UNUSED void *meta) { sfree(t->suites); } +void criterion_register_test(struct criterion_test_set *set, + struct criterion_test *test) { + + struct criterion_suite_set css = { + .suite = { .name = test->category }, + }; + struct criterion_suite_set *s = insert_ordered_set(set->suites, &css, sizeof (css)); + if (!s->tests) + s->tests = new_ordered_set(cmp_test, NULL); + + insert_ordered_set(s->tests, test, sizeof(*test)); + ++set->tests; +} + struct criterion_test_set *criterion_init(void) { struct criterion_ordered_set *suites = new_ordered_set(cmp_suite, dtor_suite_set); @@ -85,7 +107,16 @@ struct criterion_test_set *criterion_init(void) { insert_ordered_set(suites, &css, sizeof (css)); } - size_t nb_tests = 0; + struct criterion_test_set *set = smalloc( + .size = sizeof (struct criterion_test_set), + .dtor = dtor_test_set + ); + + *set = (struct criterion_test_set) { + suites, + 0, + }; + FOREACH_TEST_SEC(test) { if (!test->category) break; @@ -93,21 +124,10 @@ struct criterion_test_set *criterion_init(void) { if (!*test->category) continue; - struct criterion_suite_set css = { - .suite = { .name = test->category }, - }; - struct criterion_suite_set *s = insert_ordered_set(suites, &css, sizeof (css)); - if (!s->tests) - s->tests = new_ordered_set(cmp_test, NULL); - - insert_ordered_set(s->tests, test, sizeof(*test)); - ++nb_tests; + criterion_register_test(set, test); } - return unique_ptr(struct criterion_test_set, { - suites, - nb_tests, - }, dtor_test_set); + return set; } typedef void (*f_test_run)(struct criterion_global_stats *, @@ -126,7 +146,7 @@ static void map_tests(struct criterion_test_set *set, report(PRE_SUITE, s); log(pre_suite, s); - smart struct criterion_suite_stats *suite_stats = suite_stats_init(&s->suite); + struct criterion_suite_stats *suite_stats = suite_stats_init(&s->suite); struct event ev = { .kind = PRE_SUITE }; stat_push_event(stats, suite_stats, NULL, &ev); @@ -135,28 +155,38 @@ static void map_tests(struct criterion_test_set *set, fun(stats, suite_stats, t, &s->suite); if (criterion_options.fail_fast && stats->tests_failed > 0) break; - if (!is_runner()) + if (!is_runner()) { + sfree(suite_stats); return; + } } report(POST_SUITE, suite_stats); log(post_suite, suite_stats); + + sfree(suite_stats); } + } static void run_test_child(struct criterion_test *test, struct criterion_suite *suite) { + if (suite->data && suite->data->timeout != 0 && test->data->timeout == 0) + setup_timeout((uint64_t) (suite->data->timeout * 1e9)); + else if (test->data->timeout != 0) + setup_timeout((uint64_t) (test->data->timeout * 1e9)); + send_event(PRE_INIT, NULL, 0); if (suite->data) - (suite->data->init ?: nothing)(); - (test->data->init ?: nothing)(); + (suite->data->init ? suite->data->init : nothing)(); + (test->data->init ? test->data->init : nothing)(); send_event(PRE_TEST, NULL, 0); struct timespec_compat ts; if (setup_abort_test()) { timer_start(&ts); - (test->test ?: nothing)(); + (test->test ? test->test : nothing)(); } double elapsed_time; @@ -164,14 +194,13 @@ static void run_test_child(struct criterion_test *test, elapsed_time = -1; send_event(POST_TEST, &elapsed_time, sizeof (double)); - (test->data->fini ?: nothing)(); + (test->data->fini ? test->data->fini : nothing)(); if (suite->data) - (suite->data->fini ?: nothing)(); + (suite->data->fini ? suite->data->fini : nothing)(); send_event(POST_FINI, NULL, 0); } -__attribute__((always_inline)) -static inline bool is_disabled(struct criterion_test *t, +static INLINE bool is_disabled(struct criterion_test *t, struct criterion_suite *s) { return t->data->disabled || (s->data && s->data->disabled); @@ -191,22 +220,24 @@ static void run_test(struct criterion_global_stats *stats, struct criterion_test *test, struct criterion_suite *suite) { - smart struct criterion_test_stats *test_stats = test_stats_init(test); + struct criterion_test_stats *test_stats = test_stats_init(test); + struct process *proc = NULL; if (is_disabled(test, suite)) { stat_push_event(stats, suite_stats, test_stats, &(struct event) { .kind = PRE_INIT }); - return; + goto cleanup; } - smart struct process *proc = spawn_test_worker(test, suite, run_test_child); + proc = spawn_test_worker(test, suite, run_test_child); if (proc == NULL && !is_runner()) - return; + goto cleanup; bool test_started = false; bool normal_finish = false; + bool cleaned_up = false; struct event *ev; while ((ev = worker_read_event(proc)) != NULL) { stat_push_event(stats, suite_stats, test_stats, ev); @@ -220,6 +251,14 @@ static void run_test(struct criterion_global_stats *stats, log(pre_test, test); test_started = true; break; + case THEORY_FAIL: { + struct criterion_theory_stats ths = { + .formatted_args = (char*) ev->data, + .stats = test_stats, + }; + report(THEORY_FAIL, &ths); + log(theory_fail, &ths); + } break; case ASSERT: report(ASSERT, ev->data); log(assert, ev->data); @@ -232,6 +271,7 @@ static void run_test(struct criterion_global_stats *stats, case POST_FINI: report(POST_FINI, test_stats); log(post_fini, test_stats); + cleaned_up = true; break; } sfree(ev); @@ -239,6 +279,17 @@ static void run_test(struct criterion_global_stats *stats, struct process_status status = wait_proc(proc); if (status.kind == SIGNAL) { + if (status.status == SIGPROF) { + test_stats->timed_out = true; + double elapsed_time = test->data->timeout; + if (elapsed_time == 0 && suite->data) + elapsed_time = suite->data->timeout; + push_event(POST_TEST, .data = &elapsed_time); + push_event(POST_FINI); + log(test_timeout, test_stats); + goto cleanup; + } + if (normal_finish || !test_started) { log(other_crash, test_stats); if (!test_started) { @@ -247,7 +298,7 @@ static void run_test(struct criterion_global_stats *stats, test_stats, &(struct event) { .kind = TEST_CRASH }); } - return; + goto cleanup; } test_stats->signal = status.status; if (test->data->signal == 0) { @@ -260,7 +311,35 @@ static void run_test(struct criterion_global_stats *stats, push_event(POST_FINI); log(post_fini, test_stats); } + } else { + if ((normal_finish && !cleaned_up) || !test_started) { + log(abnormal_exit, test_stats); + if (!test_started) { + stat_push_event(stats, + suite_stats, + test_stats, + &(struct event) { .kind = TEST_CRASH }); + } + goto cleanup; + } + test_stats->exit_code = status.status; + if (!normal_finish) { + if (test->data->exit_code == 0) { + push_event(TEST_CRASH); + log(abnormal_exit, test_stats); + } else { + double elapsed_time = 0; + push_event(POST_TEST, .data = &elapsed_time); + log(post_test, test_stats); + push_event(POST_FINI); + log(post_fini, test_stats); + } + } } + +cleanup: + sfree(test_stats); + sfree(proc); } #ifdef HAVE_PCRE @@ -283,39 +362,50 @@ void disable_unmatching(struct criterion_test_set *set) { } #endif -static int criterion_run_all_tests_impl(void) { +struct criterion_test_set *criterion_initialize(void) { + init_i18n(); + if (resume_child()) // (windows only) resume from the fork - return -1; + exit(0); - smart struct criterion_test_set *set = criterion_init(); -#ifdef HAVE_PCRE - if (criterion_options.pattern) - disable_unmatching(set); -#endif + return criterion_init(); +} +void criterion_finalize(struct criterion_test_set *set) { + sfree(set); +} + +static int criterion_run_all_tests_impl(struct criterion_test_set *set) { report(PRE_ALL, set); log(pre_all, set); fflush(NULL); // flush everything before forking - smart struct criterion_global_stats *stats = stats_init(); + struct criterion_global_stats *stats = stats_init(); map_tests(set, stats, run_test); + int result = is_runner() ? stats->tests_failed == 0 : -1; + if (!is_runner()) - return -1; + goto cleanup; report(POST_ALL, stats); log(post_all, stats); - return stats->tests_failed == 0; + +cleanup: + sfree(stats); + return result; } -int criterion_run_all_tests(void) { +int criterion_run_all_tests(struct criterion_test_set *set) { + #ifdef HAVE_PCRE + if (criterion_options.pattern) + disable_unmatching(set); + #endif + set_runner_process(); - int res = criterion_run_all_tests_impl(); + int res = criterion_run_all_tests_impl(set); unset_runner_process(); - if (res == -1) // if this is the test worker terminating - exit(0); - return criterion_options.always_succeed || res; } diff --git a/src/stats.c b/src/stats.c index e7d60a69..a9dbe95c 100644 --- a/src/stats.c +++ b/src/stats.c @@ -21,24 +21,46 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include +#include +#include #include "criterion/common.h" #include "stats.h" +#include "common.h" #include -static void nothing() {}; -static void push_pre_suite(); -static void push_pre_init(); -static void push_assert(); -static void push_post_test(); -static void push_test_crash(); - typedef struct criterion_global_stats s_glob_stats; typedef struct criterion_suite_stats s_suite_stats; typedef struct criterion_test_stats s_test_stats; typedef struct criterion_assert_stats s_assert_stats; +static void push_pre_suite(s_glob_stats *stats, + s_suite_stats *sstats, + s_test_stats *tstats, + void *data); +static void push_pre_init(s_glob_stats *stats, + s_suite_stats *sstats, + s_test_stats *tstats, + void *data); +static void push_assert(s_glob_stats *stats, + s_suite_stats *sstats, + s_test_stats *tstats, + void *data); +static void push_post_test(s_glob_stats *stats, + s_suite_stats *sstats, + s_test_stats *tstats, + void *data); +static void push_test_crash(s_glob_stats *stats, + s_suite_stats *sstats, + s_test_stats *tstats, + void *data); + +static void nothing(UNUSED s_glob_stats *stats, + UNUSED s_suite_stats *sstats, + UNUSED s_test_stats *tstats, + UNUSED void *data) { +}; + static void destroy_stats(void *ptr, UNUSED void *meta) { s_glob_stats *stats = ptr; for (s_suite_stats *s = stats->suites, *next; s; s = next) { @@ -48,7 +70,12 @@ static void destroy_stats(void *ptr, UNUSED void *meta) { } s_glob_stats *stats_init(void) { - return unique_ptr(s_glob_stats, .dtor = destroy_stats); + s_glob_stats *stats = smalloc( + .size = sizeof (s_glob_stats), + .dtor = destroy_stats + ); + *stats = (s_glob_stats) { .suites = NULL }; + return stats; } static void destroy_suite_stats(void *ptr, UNUSED void *meta) { @@ -60,9 +87,13 @@ static void destroy_suite_stats(void *ptr, UNUSED void *meta) { } s_suite_stats *suite_stats_init(struct criterion_suite *s) { - return shared_ptr(s_suite_stats, { - .suite = s, - }, destroy_suite_stats); + s_suite_stats *stats = smalloc( + .size = sizeof (s_suite_stats), + .kind = SHARED, + .dtor = destroy_suite_stats + ); + *stats = (s_suite_stats) { .suite = s }; + return stats; } static void destroy_test_stats(void *ptr, UNUSED void *meta) { @@ -74,23 +105,32 @@ static void destroy_test_stats(void *ptr, UNUSED void *meta) { } s_test_stats *test_stats_init(struct criterion_test *t) { - return shared_ptr(s_test_stats, { - .test = t, - .progress = t->data->line_, - .file = t->data->file_ - }, destroy_test_stats); + s_test_stats *stats = smalloc( + .size = sizeof (s_test_stats), + .kind = SHARED, + .dtor = destroy_test_stats + ); + *stats = (s_test_stats) { + .test = t, + .progress = t->data->line_, + .file = t->data->file_ + }; + return stats; } +typedef void (*f_handle)(s_glob_stats *, s_suite_stats *, s_test_stats *, void *); + void stat_push_event(s_glob_stats *stats, s_suite_stats *suite, s_test_stats *test, struct event *data) { - static void (*const handles[])(s_glob_stats *, s_suite_stats *, s_test_stats *, void *) = { + static const f_handle handles[] = { nothing, // PRE_ALL push_pre_suite, // PRE_SUITE push_pre_init, // PRE_INIT nothing, // PRE_TEST push_assert, // ASSERT + nothing, // THEORY_FAIL push_test_crash, // TEST_CRASH push_post_test, // POST_TEST nothing, // POST_FINI @@ -113,8 +153,7 @@ static void push_pre_suite(s_glob_stats *stats, ++stats->nb_suites; } -__attribute__((always_inline)) -static inline bool is_disabled(struct criterion_test *t, +static INLINE bool is_disabled(struct criterion_test *t, struct criterion_suite *s) { return t->data->disabled || (s->data && s->data->disabled); @@ -138,8 +177,13 @@ static void push_pre_init(s_glob_stats *stats, static void push_assert(s_glob_stats *stats, s_suite_stats *suite, s_test_stats *test, - s_assert_stats *data) { - s_assert_stats *dup = unique_ptr(s_assert_stats, (*data)); + void *ptr) { + + s_assert_stats *data = ptr; + + s_assert_stats *dup = smalloc(sizeof (s_assert_stats)); + memcpy(dup, data, sizeof (s_assert_stats)); + dup->next = test->asserts; test->asserts = dup; @@ -160,16 +204,25 @@ static void push_assert(s_glob_stats *stats, static void push_post_test(s_glob_stats *stats, s_suite_stats *suite, s_test_stats *test, - double *ptr) { - test->elapsed_time = *ptr; - if (test->failed_asserts > 0 || test->signal != test->test->data->signal) { + void *ptr) { + double *data = ptr; + + test->elapsed_time = (float) *data; + if (test->failed_asserts > 0 + || test->timed_out + || test->signal != test->test->data->signal + || test->exit_code != test->test->data->exit_code) { test->failed = 1; + } + + if (test->failed) { ++stats->tests_failed; ++suite->tests_failed; } else { ++stats->tests_passed; ++suite->tests_passed; } + } static void push_test_crash(s_glob_stats *stats, diff --git a/src/theories.c b/src/theories.c new file mode 100644 index 00000000..9e440ff5 --- /dev/null +++ b/src/theories.c @@ -0,0 +1,243 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include "criterion/theories.h" +#include "abort.h" + +struct criterion_theory_context { + DCCallVM* vm; +}; + +void cr_theory_push_arg(struct criterion_theory_context *ctx, bool is_float, size_t size, void *ptr) { + if (is_float) { + if (size == sizeof (float)) { + dcArgFloat(ctx->vm, *(float*)ptr); + } else if (size == sizeof (double)) { + dcArgDouble(ctx->vm, *(double*)ptr); + } else if (size == sizeof (long double)) { + dcArgDouble(ctx->vm, *(long double*)ptr); + } + } else { + if (size == sizeof (char)) { + dcArgChar(ctx->vm, *(char*)ptr); + } else if (size == sizeof (short)) { + dcArgShort(ctx->vm, *(short*)ptr); + } else if (size == sizeof (int)) { + dcArgInt(ctx->vm, *(int*)ptr); + } else if (size == sizeof (bool)) { + dcArgBool(ctx->vm, *(bool*)ptr); +#if INT_MAX < LONG_MAX + } else if (size == sizeof (long)) { + dcArgLong(ctx->vm, *(long*)ptr); +#endif +#if LONG_MAX < LLONG_MAX + } else if (size == sizeof (long long)) { + dcArgLongLong(ctx->vm, *(long long*)ptr); +#endif + } else if (size == sizeof (void*)) { + dcArgPointer(ctx->vm, *(void**)ptr); + } else { + dcArgPointer(ctx->vm, ptr); + } + } +} + +struct criterion_theory_context* cr_theory_init(void) { + struct criterion_theory_context* ctx = malloc(sizeof (struct criterion_theory_context)); + ctx->vm = dcNewCallVM(4096); + dcMode(ctx->vm, DC_CALL_C_DEFAULT); + return ctx; +} + +void cr_theory_free(struct criterion_theory_context *ctx) { + dcFree(ctx->vm); +} + +static jmp_buf theory_jmp; + +void cr_theory_abort(void) { + longjmp(theory_jmp, 1); +} + +int cr_theory_mark(void) { + return setjmp(theory_jmp); +} + +void cr_theory_reset(struct criterion_theory_context *ctx) { + dcReset(ctx->vm); +} + +void cr_theory_call(struct criterion_theory_context *ctx, void (*fnptr)(void)) { + dcCallVoid(ctx->vm, (DCpointer) fnptr); +} + +static bool contains_word(const char *str, const char *pattern, size_t sz) { + char *res = strstr(str, pattern); + + return res + && (res == str || (res > str && res[-1] == ' ')) + && (!res[sz - 1] || res[sz - 1] == ' '); +} + +static bool is_string(const char *name) { + return !strcmp(name, "char*") + || !strcmp(name, "char *") + || !strcmp(name, "const char*") + || !strcmp(name, "const char *") + || !strcmp(name, "char const *") + || !strcmp(name, "char const*") + || !strcmp(name, "char[]") + || !strcmp(name, "char []") + || !strcmp(name, "const char[]") + || !strcmp(name, "const char []") + || !strcmp(name, "char const[]") + || !strcmp(name, "char const []"); +} + +static bool is_float(const char *name) { + return contains_word(name, "float", sizeof ("float")) + || contains_word(name, "double", sizeof ("double")); +} + +static bool is_unsigned_int(const char *name) { + return contains_word(name, "unsigned", sizeof ("unsigned")) + || !strncmp(name, "uint", 4); +} + +static bool is_bool(const char *name) { + return contains_word(name, "bool", sizeof ("bool")) + || contains_word(name, "_Bool", sizeof ("_Bool")); +} + +static void format_arg(char (*arg)[1024], struct criterion_datapoints *dp, void *data) { + if (is_float(dp->name)) { + if (dp->size == sizeof (float)) { + snprintf(*arg, sizeof (*arg) - 1, "%gf", *(float*) data); + } else if (dp->size == sizeof (double)) { + snprintf(*arg, sizeof (*arg) - 1, "%g", *(double*) data); + } else if (dp->size == sizeof (long double)) { + snprintf(*arg, sizeof (*arg) - 1, "%gl", (double) *(long double*) data); + } + } else { + if (is_string(dp->name)) { + snprintf(*arg, sizeof (*arg) - 1, "\"%s\"", *(char**) data); + } else if (dp->size == sizeof (bool) && is_bool(dp->name)) { + snprintf(*arg, sizeof (*arg) - 1, "%s", (*(bool*) data) ? "true" : "false"); + } else if (dp->size == sizeof (char)) { + snprintf(*arg, sizeof (*arg) - 1, "'%c'", *(char*) data); + } else if (dp->size == sizeof (short)) { + const char *fmt = is_unsigned_int(dp->name) ? "%hu" : "%hd"; + snprintf(*arg, sizeof (*arg) - 1, fmt, *(short*) data); + } else if (dp->size == sizeof (int)) { + const char *fmt = is_unsigned_int(dp->name) ? "%u" : "%d"; + snprintf(*arg, sizeof (*arg) - 1, fmt, *(int*) data); + } else if (dp->size == sizeof (void*) && strstr(dp->name, "*")) { + snprintf(*arg, sizeof (*arg) - 1, "%p", *(void**) data); +#if INT_MAX < LONG_MAX + } else if (dp->size == sizeof (long)) { + const char *fmt = is_unsigned_int(dp->name) ? "%lulu" : "%ldl"; + snprintf(*arg, sizeof (*arg) - 1, fmt, *(long*) data); +#endif +#if LONG_MAX < LLONG_MAX + } else if (dp->size == sizeof (long long)) { + const char *fmt = is_unsigned_int(dp->name) ? "%llullu" : "%lldll"; + snprintf(*arg, sizeof (*arg) - 1, fmt, *(long long*) data); +#endif + } else { + snprintf(*arg, sizeof (*arg) - 1, "%s", ""); + } + } +} + +static void concat_arg(char (*msg)[4096], struct criterion_datapoints *dps, size_t *indices, size_t i) { + void *data = ((char*) dps[i].arr) + dps[i].size * indices[i]; + + char arg[1024]; + format_arg(&arg, dps + i, data); + strncat(*msg, arg, sizeof (*msg) - 1); +} + +void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (*fnptr)(void)) { + struct criterion_theory_context *ctx = cr_theory_init(); + + size_t *indices = malloc(sizeof (size_t) * datapoints); + memset(indices, 0, datapoints * sizeof (size_t)); + + bool has_next = true; + while (has_next) { + if (!cr_theory_mark()) { + cr_theory_reset(ctx); + for (size_t i = 0; i < datapoints; ++i) { + + bool is_float = contains_word(dps[i].name, "float", sizeof ("float")) + || contains_word(dps[i].name, "double", sizeof ("double")); + + cr_theory_push_arg(ctx, + is_float, + dps[i].size, + ((char*) dps[i].arr) + dps[i].size * indices[i]); + } + + jmp_buf backup; + memcpy(backup, g_pre_test, sizeof (jmp_buf)); + if (!setjmp(g_pre_test)) { + cr_theory_call(ctx, fnptr); + } else { + struct { + size_t len; + char msg[4096]; + } result = { .len = 0 }; + + for (size_t i = 0; i < datapoints - 1; ++i) { + concat_arg(&result.msg, dps, indices, i); + strncat(result.msg, ", ", sizeof (result.msg) - 1); + } + concat_arg(&result.msg, dps, indices, datapoints - 1); + result.len = strlen(result.msg) + 1; + + send_event(THEORY_FAIL, &result, result.len + sizeof (size_t)); + } + memcpy(g_pre_test, backup, sizeof (jmp_buf)); + } + + for (size_t i = 0; i < datapoints; ++i) { + if (indices[i] == dps[i].len - 1) { + indices[i] = 0; + has_next = i != (datapoints - 1); + } else { + ++indices[i]; + break; + } + } + } + + free(indices); + cr_theory_free(ctx); +} diff --git a/src/timer.c b/src/timer.c index edb181e5..8007afa2 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,6 +1,9 @@ #include #include +#include #include "timer.h" +#include "criterion/common.h" +#include "posix-compat.h" #define GIGA 1000000000 @@ -17,6 +20,7 @@ extern __attribute__ ((weak)) int clock_gettime(clockid_t, struct timespec *); #elif defined(__APPLE__) # include # include +# include #elif defined(_WIN32) || defined(__CYGWIN__) # define VC_EXTRALEAN # define WIN32_LEAN_AND_MEAN @@ -81,3 +85,70 @@ int timer_end(double *time, struct timespec_compat *state) { *time = (last.tv_sec - state->tv_sec) + (last.tv_nsec - state->tv_nsec) / (double) GIGA; return 1; } + +#if defined(_WIN32) || defined(__CYGWIN__) +DWORD WINAPI win_raise_timeout(LPVOID ptr) { + uint64_t *nanos = (uint64_t*) ptr; + Sleep(*nanos / 1000000); + TerminateProcess(GetCurrentProcess(), CR_EXCEPTION_TIMEOUT); + return 0; +} +#endif + +#if defined(__APPLE__) +void *wait_and_raise(void *ptr) { + uint64_t *nanos = ptr; + struct timespec elapsed = { + .tv_sec = *nanos / GIGA, + .tv_nsec = *nanos % GIGA, + }; + free(nanos); + if (!nanosleep(&elapsed, NULL)) + raise(SIGPROF); + return NULL; +} +#endif + +int setup_timeout(uint64_t nanos) { +#if defined(__APPLE__) + uint64_t *nanos_copy = malloc(sizeof (uint64_t)); + *nanos_copy = nanos; + + pthread_t thread; + int res = pthread_create(&thread, NULL, wait_and_raise, nanos_copy); + + return res ? -1 : 0; +#elif defined(_WIN32) || defined(__CYGWIN__) + uint64_t *nanos_copy = malloc(sizeof (uint64_t)); + *nanos_copy = nanos; + + HANDLE thread = CreateThread(NULL, 0, win_raise_timeout, nanos_copy, 0, NULL); + if (thread == NULL) + return -1; + CloseHandle(thread); + return 0; +#elif defined(__unix__) + if (!can_measure_time()) { + errno = ENOTSUP; + return -1; + } + + timer_t timer; + int res = timer_create(CLOCK_MONOTONIC, &(struct sigevent) { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = SIGPROF, + }, &timer); + + if (res == -1) + return res; + + struct itimerspec schedule = { + .it_value = { .tv_sec = nanos / GIGA, .tv_nsec = (nanos % GIGA) } + }; + + return timer_settime(timer, 0, &schedule, NULL); +#else + errno = ENOTSUP; + return -1; +#endif +} diff --git a/src/timer.h b/src/timer.h index 2d334cbf..14f301d9 100644 --- a/src/timer.h +++ b/src/timer.h @@ -1,6 +1,7 @@ #ifndef TIMER_H_ # define TIMER_H_ +# include # include # include # include @@ -13,5 +14,6 @@ struct timespec_compat { bool can_measure_time(void); int timer_start(struct timespec_compat *state); int timer_end(double *time, struct timespec_compat *state); +int setup_timeout(uint64_t nanos); #endif /* !TIMER_H_ */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..d9122f24 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,16 @@ +if (NOT MSVC) + set(CMAKE_C_FLAGS "-std=gnu99 -Wall -Wextra") +endif () + +include_directories(../include ../src) + +set(TEST_SOURCES + ordered-set.c +) + +add_executable(criterion_unit_tests EXCLUDE_FROM_ALL ${TEST_SOURCES}) +target_link_libraries(criterion_unit_tests criterion) + +add_dependencies(criterion_tests criterion_unit_tests) + +add_test(criterion_unit_tests criterion_unit_tests) diff --git a/test/ordered-set.c b/test/ordered-set.c new file mode 100644 index 00000000..3ecdbf1b --- /dev/null +++ b/test/ordered-set.c @@ -0,0 +1,48 @@ +#include + +#include "criterion/criterion.h" +#include "criterion/ordered-set.h" + +int compare_gt(void *a, void *b) { + int *ia = a, *ib = b; + return *ia == *ib ? 0 : (*ia > *ib ? -1 : 1); +} + +int compare_lt(void *a, void *b) { + int *ia = a, *ib = b; + return *ia == *ib ? 0 : (*ia < *ib ? -1 : 1); +} + +Test(ordered_set, contract_lt) { + struct criterion_ordered_set *set = new_ordered_set(compare_lt, NULL); + + insert_ordered_set(set, &(int[1]) { 1 }, sizeof (int)); + insert_ordered_set(set, &(int[1]) { 8 }, sizeof (int)); + insert_ordered_set(set, &(int[1]) { 3 }, sizeof (int)); + + int *prev = NULL; + FOREACH_SET(int *e, set) { + if (prev) + cr_assert_lt(*prev, *e); + prev = e; + } + + sfree(set); +} + +Test(ordered_set, contract_gt) { + struct criterion_ordered_set *set = new_ordered_set(compare_gt, NULL); + + insert_ordered_set(set, &(int[1]) { 1 }, sizeof (int)); + insert_ordered_set(set, &(int[1]) { 8 }, sizeof (int)); + insert_ordered_set(set, &(int[1]) { 3 }, sizeof (int)); + + int *prev = NULL; + FOREACH_SET(int *e, set) { + if (prev) + cr_assert_gt(*prev, *e); + prev = e; + } + + sfree(set); +}