diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 69c8dbb..654fcff 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,10 +1,12 @@ [bumpversion] -current_version = 1.2.2 +current_version = 1.3.0 commit = True -[bumpversion:file:configure.ac] +[bumpversion:file:CMakeLists.txt] [bumpversion:file:doc/conf.py] [bumpversion:file:appveyor.yml] +[bumpversion:file:README.md] + diff --git a/.ci/install-libcsptr.sh b/.ci/install-libcsptr.sh new file mode 100755 index 0000000..088f06b --- /dev/null +++ b/.ci/install-libcsptr.sh @@ -0,0 +1,14 @@ +#!/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/.cmake/Modules/Coveralls.cmake b/.cmake/Modules/Coveralls.cmake new file mode 100644 index 0000000..4250397 --- /dev/null +++ b/.cmake/Modules/Coveralls.cmake @@ -0,0 +1,128 @@ +# +# The MIT License (MIT) +# +# 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. +# +# Copyright (C) 2014 Joakim Söderberg +# + +set(_CMAKE_SCRIPT_PATH ${CMAKE_CURRENT_LIST_DIR}) # must be outside coveralls_setup() to get correct path + +# +# Param _COVERAGE_SRCS A list of source files that coverage should be collected for. +# Param _COVERALLS_UPLOAD Upload the result to coveralls? +# + +function(coveralls_setup _COVERAGE_SRCS _COVERALLS_UPLOAD) + + if (ARGC GREATER 2) + set(_CMAKE_SCRIPT_PATH ${ARGN}) + message(STATUS "Coveralls: Using alternate CMake script dir: ${_CMAKE_SCRIPT_PATH}") + endif() + + if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake") + message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake") + endif() + + if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake") + message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake") + endif() + + # When passing a CMake list to an external process, the list + # will be converted from the format "1;2;3" to "1 2 3". + # This means the script we're calling won't see it as a list + # of sources, but rather just one long path. We remedy this + # by replacing ";" with "*" and then reversing that in the script + # that we're calling. + # http://cmake.3232098.n2.nabble.com/Passing-a-CMake-list-quot-as-is-quot-to-a-custom-target-td6505681.html + set(COVERAGE_SRCS_TMP ${_COVERAGE_SRCS}) + set(COVERAGE_SRCS "") + foreach (COVERAGE_SRC ${COVERAGE_SRCS_TMP}) + set(COVERAGE_SRCS "${COVERAGE_SRCS}*${COVERAGE_SRC}") + endforeach() + + #message("Coverage sources: ${COVERAGE_SRCS}") + set(COVERALLS_FILE ${PROJECT_BINARY_DIR}/coveralls.json) + + add_custom_target(coveralls_generate + + # Zero the coverage counters. + COMMAND ${CMAKE_COMMAND} -DPROJECT_BINARY_DIR="${PROJECT_BINARY_DIR}" -P "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake" + + # Run regress tests. + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + + # Generate Gcov and translate it into coveralls JSON. + # We do this by executing an external CMake script. + # (We don't want this to run at CMake generation time, but after compilation and everything has run). + COMMAND ${CMAKE_COMMAND} + -DCOVERAGE_SRCS="${COVERAGE_SRCS}" # TODO: This is passed like: "a b c", not "a;b;c" + -DCOVERALLS_OUTPUT_FILE="${COVERALLS_FILE}" + -DCOV_PATH="${PROJECT_BINARY_DIR}" + -DPROJECT_ROOT="${PROJECT_SOURCE_DIR}" + -P "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake" + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coveralls output..." + ) + + if (_COVERALLS_UPLOAD) + message("COVERALLS UPLOAD: ON") + + find_program(CURL_EXECUTABLE curl) + + if (NOT CURL_EXECUTABLE) + message(FATAL_ERROR "Coveralls: curl not found! Aborting") + endif() + + add_custom_target(coveralls_upload + # Upload the JSON to coveralls. + COMMAND ${CURL_EXECUTABLE} + -S -F json_file=@${COVERALLS_FILE} + https://coveralls.io/api/v1/jobs + + DEPENDS coveralls_generate + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Uploading coveralls output...") + + add_custom_target(coveralls DEPENDS coveralls_upload) + else() + message("COVERALLS UPLOAD: OFF") + add_custom_target(coveralls DEPENDS coveralls_generate) + endif() + +endfunction() + +macro(coveralls_turn_on_coverage) + if(NOT (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + AND (NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")) + message(FATAL_ERROR "Coveralls: Compiler ${CMAKE_C_COMPILER_ID} is not GNU gcc! Aborting... You can set this on the command line using CC=/usr/bin/gcc CXX=/usr/bin/g++ cmake ..") + endif() + + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(FATAL_ERROR "Coveralls: Code coverage results with an optimised (non-Debug) build may be misleading! Add -DCMAKE_BUILD_TYPE=Debug") + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") +endmacro() + + + diff --git a/.cmake/Modules/CoverallsClear.cmake b/.cmake/Modules/CoverallsClear.cmake new file mode 100644 index 0000000..7206886 --- /dev/null +++ b/.cmake/Modules/CoverallsClear.cmake @@ -0,0 +1,31 @@ +# +# The MIT License (MIT) +# +# 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. +# +# Copyright (C) 2014 Joakim Söderberg +# + +# do not follow symlinks in file(GLOB_RECURSE ...) +cmake_policy(SET CMP0009 NEW) + +file(GLOB_RECURSE GCDA_FILES "${PROJECT_BINARY_DIR}/*.gcda") +if(NOT GCDA_FILES STREQUAL "") + file(REMOVE ${GCDA_FILES}) +endif() diff --git a/.cmake/Modules/CoverallsGenerateGcov.cmake b/.cmake/Modules/CoverallsGenerateGcov.cmake new file mode 100644 index 0000000..429b695 --- /dev/null +++ b/.cmake/Modules/CoverallsGenerateGcov.cmake @@ -0,0 +1,480 @@ +# +# The MIT License (MIT) +# +# 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. +# +# Copyright (C) 2014 Joakim Söderberg +# +# This is intended to be run by a custom target in a CMake project like this. +# 0. Compile program with coverage support. +# 1. Clear coverage data. (Recursively delete *.gcda in build dir) +# 2. Run the unit tests. +# 3. Run this script specifying which source files the coverage should be performed on. +# +# This script will then use gcov to generate .gcov files in the directory specified +# via the COV_PATH var. This should probably be the same as your cmake build dir. +# +# It then parses the .gcov files to convert them into the Coveralls JSON format: +# https://coveralls.io/docs/api +# +# Example for running as standalone CMake script from the command line: +# (Note it is important the -P is at the end...) +# $ cmake -DCOV_PATH=$(pwd) +# -DCOVERAGE_SRCS="catcierge_rfid.c;catcierge_timer.c" +# -P ../cmake/CoverallsGcovUpload.cmake +# +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) + + +# +# Make sure we have the needed arguments. +# +if (NOT COVERALLS_OUTPUT_FILE) + message(FATAL_ERROR "Coveralls: No coveralls output file specified. Please set COVERALLS_OUTPUT_FILE") +endif() + +if (NOT COV_PATH) + message(FATAL_ERROR "Coveralls: Missing coverage directory path where gcov files will be generated. Please set COV_PATH") +endif() + +if (NOT COVERAGE_SRCS) + message(FATAL_ERROR "Coveralls: Missing the list of source files that we should get the coverage data for COVERAGE_SRCS") +endif() + +if (NOT PROJECT_ROOT) + message(FATAL_ERROR "Coveralls: Missing PROJECT_ROOT.") +endif() + +# Since it's not possible to pass a CMake list properly in the +# "1;2;3" format to an external process, we have replaced the +# ";" with "*", so reverse that here so we get it back into the +# CMake list format. +string(REGEX REPLACE "\\*" ";" COVERAGE_SRCS ${COVERAGE_SRCS}) + +# convert all paths in COVERAGE_SRCS to absolute paths +set(COVERAGE_SRCS_TMP "") +foreach (COVERAGE_SRC ${COVERAGE_SRCS}) + if (NOT "${COVERAGE_SRC}" MATCHES "^/") + set(COVERAGE_SRC ${PROJECT_ROOT}/${COVERAGE_SRC}) + endif() + list(APPEND COVERAGE_SRCS_TMP ${COVERAGE_SRC}) +endforeach() +set(COVERAGE_SRCS ${COVERAGE_SRCS_TMP}) +unset(COVERAGE_SRCS_TMP) + +if (NOT DEFINED ENV{GCOV}) + find_program(GCOV_EXECUTABLE gcov) +else() + find_program(GCOV_EXECUTABLE $ENV{GCOV}) +endif() + +if (NOT GCOV_EXECUTABLE) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() + +find_package(Git) + +set(JSON_REPO_TEMPLATE + "{ + \"head\": { + \"id\": \"\@GIT_COMMIT_HASH\@\", + \"author_name\": \"\@GIT_AUTHOR_NAME\@\", + \"author_email\": \"\@GIT_AUTHOR_EMAIL\@\", + \"committer_name\": \"\@GIT_COMMITTER_NAME\@\", + \"committer_email\": \"\@GIT_COMMITTER_EMAIL\@\", + \"message\": \"\@GIT_COMMIT_MESSAGE\@\" + }, + \"branch\": \"@GIT_BRANCH@\", + \"remotes\": [] + }" +) + +# TODO: Fill in git remote data +if (GIT_FOUND) + # Branch. + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + macro (git_log_format FORMAT_CHARS VAR_NAME) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%${FORMAT_CHARS} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE ${VAR_NAME} + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endmacro() + + git_log_format(an GIT_AUTHOR_NAME) + git_log_format(ae GIT_AUTHOR_EMAIL) + git_log_format(cn GIT_COMMITTER_NAME) + git_log_format(ce GIT_COMMITTER_EMAIL) + git_log_format(B GIT_COMMIT_MESSAGE) + git_log_format(H GIT_COMMIT_HASH) + + message("Git exe: ${GIT_EXECUTABLE}") + message("Git branch: ${GIT_BRANCH}") + message("Git author: ${GIT_AUTHOR_NAME}") + message("Git e-mail: ${GIT_AUTHOR_EMAIL}") + message("Git commiter name: ${GIT_COMMITTER_NAME}") + message("Git commiter e-mail: ${GIT_COMMITTER_EMAIL}") + message("Git commit hash: ${GIT_COMMIT_HASH}") + message("Git commit message: ${GIT_COMMIT_MESSAGE}") + + string(CONFIGURE ${JSON_REPO_TEMPLATE} JSON_REPO_DATA) +else() + set(JSON_REPO_DATA "{}") +endif() + +############################# Macros ######################################### + +# +# This macro converts from the full path format gcov outputs: +# +# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov +# +# to the original source file path the .gcov is for: +# +# /path/to/project/root/subdir/the_file.c +# +macro(get_source_path_from_gcov_filename _SRC_FILENAME _GCOV_FILENAME) + + # /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov + # -> + # #path#to#project#root#subdir#the_file.c.gcov + get_filename_component(_GCOV_FILENAME_WEXT ${_GCOV_FILENAME} NAME) + + # #path#to#project#root#subdir#the_file.c.gcov -> /path/to/project/root/subdir/the_file.c + string(REGEX REPLACE "\\.gcov$" "" SRC_FILENAME_TMP ${_GCOV_FILENAME_WEXT}) + string(REGEX REPLACE "\#" "/" SRC_FILENAME_TMP ${SRC_FILENAME_TMP}) + string(REGEX REPLACE "~" ":" SRC_FILENAME_TMP ${SRC_FILENAME_TMP}) + set(${_SRC_FILENAME} "${SRC_FILENAME_TMP}") +endmacro() + +############################################################################## + +# Get the coverage data. +file(GLOB_RECURSE GCDA_FILES "${COV_PATH}/*.gcda") +message("GCDA files:") + +# Get a list of all the object directories needed by gcov +# (The directories the .gcda files and .o files are found in) +# and run gcov on those. +foreach(GCDA ${GCDA_FILES}) + message("Process: ${GCDA}") + message("------------------------------------------------------------------------------") + get_filename_component(GCDA_DIR ${GCDA} PATH) + + # + # The -p below refers to "Preserve path components", + # This means that the generated gcov filename of a source file will + # keep the original files entire filepath, but / is replaced with #. + # Example: + # + # /path/to/project/root/build/CMakeFiles/the_file.dir/subdir/the_file.c.gcda + # ------------------------------------------------------------------------------ + # File '/path/to/project/root/subdir/the_file.c' + # Lines executed:68.34% of 199 + # /path/to/project/root/subdir/the_file.c:creating '#path#to#project#root#subdir#the_file.c.gcov' + # + # If -p is not specified then the file is named only "the_file.c.gcov" + # + execute_process( + COMMAND ${GCOV_EXECUTABLE} -p -o ${GCDA_DIR} ${GCDA} + WORKING_DIRECTORY ${COV_PATH} + ) +endforeach() + +# TODO: Make these be absolute path +file(GLOB ALL_GCOV_FILES ${COV_PATH}/*.gcov) + +# Get only the filenames to use for filtering. +#set(COVERAGE_SRCS_NAMES "") +#foreach (COVSRC ${COVERAGE_SRCS}) +# get_filename_component(COVSRC_NAME ${COVSRC} NAME) +# message("${COVSRC} -> ${COVSRC_NAME}") +# list(APPEND COVERAGE_SRCS_NAMES "${COVSRC_NAME}") +#endforeach() + +# +# Filter out all but the gcov files we want. +# +# We do this by comparing the list of COVERAGE_SRCS filepaths that the +# user wants the coverage data for with the paths of the generated .gcov files, +# so that we only keep the relevant gcov files. +# +# Example: +# COVERAGE_SRCS = +# /path/to/project/root/subdir/the_file.c +# +# ALL_GCOV_FILES = +# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov +# /path/to/project/root/build/#path#to#project#root#subdir#other_file.c.gcov +# +# Result should be: +# GCOV_FILES = +# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov +# +set(GCOV_FILES "") +#message("Look in coverage sources: ${COVERAGE_SRCS}") +message("\nFilter out unwanted GCOV files:") +message("===============================") + +set(COVERAGE_SRCS_REMAINING ${COVERAGE_SRCS}) + +foreach (GCOV_FILE ${ALL_GCOV_FILES}) + + # + # /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov + # -> + # /path/to/project/root/subdir/the_file.c + get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE}) + file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}") + + # Is this in the list of source files? + # TODO: We want to match against relative path filenames from the source file root... + list(FIND COVERAGE_SRCS ${GCOV_SRC_PATH} WAS_FOUND) + + if (NOT WAS_FOUND EQUAL -1) + message("YES: ${GCOV_FILE}") + list(APPEND GCOV_FILES ${GCOV_FILE}) + + # We remove it from the list, so we don't bother searching for it again. + # Also files left in COVERAGE_SRCS_REMAINING after this loop ends should + # have coverage data generated from them (no lines are covered). + list(REMOVE_ITEM COVERAGE_SRCS_REMAINING ${GCOV_SRC_PATH}) + else() + message("NO: ${GCOV_FILE}") + endif() +endforeach() + +# TODO: Enable setting these +set(JSON_SERVICE_NAME "travis-ci") +set(JSON_SERVICE_JOB_ID $ENV{TRAVIS_JOB_ID}) +set(JSON_REPO_TOKEN $ENV{COVERALLS_REPO_TOKEN}) + +set(JSON_TEMPLATE +"{ + \"repo_token\": \"\@JSON_REPO_TOKEN\@\", + \"service_name\": \"\@JSON_SERVICE_NAME\@\", + \"service_job_id\": \"\@JSON_SERVICE_JOB_ID\@\", + \"source_files\": \@JSON_GCOV_FILES\@, + \"git\": \@JSON_REPO_DATA\@ +}" +) + +set(SRC_FILE_TEMPLATE +"{ + \"name\": \"\@GCOV_SRC_REL_PATH\@\", + \"source_digest\": \"\@GCOV_CONTENTS_MD5\@\", + \"coverage\": \@GCOV_FILE_COVERAGE\@ + }" +) + +message("\nGenerate JSON for files:") +message("=========================") + +set(JSON_GCOV_FILES "[") + +# Read the GCOV files line by line and get the coverage data. +foreach (GCOV_FILE ${GCOV_FILES}) + + get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE}) + file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}") + + # The new coveralls API doesn't need the entire source (Yay!) + # However, still keeping that part for now. Will cleanup in the future. + file(MD5 "${GCOV_SRC_PATH}" GCOV_CONTENTS_MD5) + message("MD5: ${GCOV_SRC_PATH} = ${GCOV_CONTENTS_MD5}") + + # Loads the gcov file as a list of lines. + # (We first open the file and replace all occurences of [] with _ + # because CMake will fail to parse a line containing unmatched brackets... + # also the \ to escaped \n in macros screws up things.) + # https://public.kitware.com/Bug/view.php?id=15369 + file(READ ${GCOV_FILE} GCOV_CONTENTS) + string(REPLACE "[" "_" GCOV_CONTENTS "${GCOV_CONTENTS}") + string(REPLACE "]" "_" GCOV_CONTENTS "${GCOV_CONTENTS}") + string(REPLACE "\\" "_" GCOV_CONTENTS "${GCOV_CONTENTS}") + + # Remove file contents to avoid encoding issues (cmake 2.8 has no ENCODING option) + string(REGEX REPLACE "([^:]*):([^:]*):([^\n]*)\n" "\\1:\\2: \n" GCOV_CONTENTS "${GCOV_CONTENTS}") + file(WRITE ${GCOV_FILE}_tmp "${GCOV_CONTENTS}") + + file(STRINGS ${GCOV_FILE}_tmp GCOV_LINES) + list(LENGTH GCOV_LINES LINE_COUNT) + + # Instead of trying to parse the source from the + # gcov file, simply read the file contents from the source file. + # (Parsing it from the gcov is hard because C-code uses ; in many places + # which also happens to be the same as the CMake list delimeter). + file(READ ${GCOV_SRC_PATH} GCOV_FILE_SOURCE) + + string(REPLACE "\\" "\\\\" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REGEX REPLACE "\"" "\\\\\"" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REPLACE "\t" "\\\\t" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REPLACE "\r" "\\\\r" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REPLACE "\n" "\\\\n" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + # According to http://json.org/ these should be escaped as well. + # Don't know how to do that in CMake however... + #string(REPLACE "\b" "\\\\b" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + #string(REPLACE "\f" "\\\\f" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + #string(REGEX REPLACE "\u([a-fA-F0-9]{4})" "\\\\u\\1" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + + # We want a json array of coverage data as a single string + # start building them from the contents of the .gcov + set(GCOV_FILE_COVERAGE "[") + + set(GCOV_LINE_COUNT 1) # Line number for the .gcov. + set(DO_SKIP 0) + foreach (GCOV_LINE ${GCOV_LINES}) + #message("${GCOV_LINE}") + # Example of what we're parsing: + # Hitcount |Line | Source + # " 8: 26: if (!allowed || (strlen(allowed) == 0))" + string(REGEX REPLACE + "^([^:]*):([^:]*):(.*)$" + "\\1;\\2;\\3" + RES + "${GCOV_LINE}") + + # Check if we should exclude lines using the Lcov syntax. + string(REGEX MATCH "LCOV_EXCL_START" START_SKIP "${GCOV_LINE}") + string(REGEX MATCH "LCOV_EXCL_END" END_SKIP "${GCOV_LINE}") + string(REGEX MATCH "LCOV_EXCL_LINE" LINE_SKIP "${GCOV_LINE}") + + set(RESET_SKIP 0) + if (LINE_SKIP AND NOT DO_SKIP) + set(DO_SKIP 1) + set(RESET_SKIP 1) + endif() + + if (START_SKIP) + set(DO_SKIP 1) + message("${GCOV_LINE_COUNT}: Start skip") + endif() + + if (END_SKIP) + set(DO_SKIP 0) + endif() + + list(LENGTH RES RES_COUNT) + + if (RES_COUNT GREATER 2) + list(GET RES 0 HITCOUNT) + list(GET RES 1 LINE) + list(GET RES 2 SOURCE) + + string(STRIP ${HITCOUNT} HITCOUNT) + string(STRIP ${LINE} LINE) + + # Lines with 0 line numbers are metadata and can be ignored. + if (NOT ${LINE} EQUAL 0) + + if (DO_SKIP) + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ") + else() + # Translate the hitcount into valid JSON values. + if (${HITCOUNT} STREQUAL "#####" OR ${HITCOUNT} STREQUAL "=====") + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}0, ") + elseif (${HITCOUNT} STREQUAL "-") + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ") + else() + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}${HITCOUNT}, ") + endif() + endif() + endif() + else() + message(WARNING "Failed to properly parse line (RES_COUNT = ${RES_COUNT}) ${GCOV_FILE}:${GCOV_LINE_COUNT}\n-->${GCOV_LINE}") + endif() + + if (RESET_SKIP) + set(DO_SKIP 0) + endif() + math(EXPR GCOV_LINE_COUNT "${GCOV_LINE_COUNT}+1") + endforeach() + + message("${GCOV_LINE_COUNT} of ${LINE_COUNT} lines read!") + + # Advanced way of removing the trailing comma in the JSON array. + # "[1, 2, 3, " -> "[1, 2, 3" + string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE}) + + # Append the trailing ] to complete the JSON array. + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]") + + # Generate the final JSON for this file. + message("Generate JSON for file: ${GCOV_SRC_REL_PATH}...") + string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON) + + set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ") +endforeach() + +# Loop through all files we couldn't find any coverage for +# as well, and generate JSON for those as well with 0% coverage. +foreach(NOT_COVERED_SRC ${COVERAGE_SRCS_REMAINING}) + + # Set variables for json replacement + set(GCOV_SRC_PATH ${NOT_COVERED_SRC}) + file(MD5 "${GCOV_SRC_PATH}" GCOV_CONTENTS_MD5) + file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}") + + # Loads the source file as a list of lines. + file(STRINGS ${NOT_COVERED_SRC} SRC_LINES) + + set(GCOV_FILE_COVERAGE "[") + set(GCOV_FILE_SOURCE "") + + foreach (SOURCE ${SRC_LINES}) + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ") + + string(REPLACE "\\" "\\\\" SOURCE "${SOURCE}") + string(REGEX REPLACE "\"" "\\\\\"" SOURCE "${SOURCE}") + string(REPLACE "\t" "\\\\t" SOURCE "${SOURCE}") + string(REPLACE "\r" "\\\\r" SOURCE "${SOURCE}") + set(GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}${SOURCE}\\n") + endforeach() + + # Remove trailing comma, and complete JSON array with ] + string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE}) + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]") + + # Generate the final JSON for this file. + message("Generate JSON for non-gcov file: ${NOT_COVERED_SRC}...") + string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON) + set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ") +endforeach() + +# Get rid of trailing comma. +string(REGEX REPLACE ",[ ]*$" "" JSON_GCOV_FILES ${JSON_GCOV_FILES}) +set(JSON_GCOV_FILES "${JSON_GCOV_FILES}]") + +# Generate the final complete JSON! +message("Generate final JSON...") +string(CONFIGURE ${JSON_TEMPLATE} JSON) + +file(WRITE "${COVERALLS_OUTPUT_FILE}" "${JSON}") +message("###########################################################################") +message("Generated coveralls JSON containing coverage data:") +message("${COVERALLS_OUTPUT_FILE}") +message("###########################################################################") + diff --git a/.cmake/Modules/FindLibcsptr.cmake b/.cmake/Modules/FindLibcsptr.cmake new file mode 100644 index 0000000..cd5e52e --- /dev/null +++ b/.cmake/Modules/FindLibcsptr.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 libcsptr +# Find the native libcsptr headers and libraries. +# +# CSPTR_INCLUDE_DIRS - where to find smart_ptr.h, etc. +# CSPTR_LIBRARIES - List of libraries when using libcsptr. +# CSPTR_FOUND - True if libcsptr has been found. + +# Look for the header file. +FIND_PATH(CSPTR_INCLUDE_DIR csptr/smart_ptr.h PATH_SUFFIXES csptr) + +# Look for the library. +FIND_LIBRARY(CSPTR_LIBRARY NAMES csptr) + +# Handle the QUIETLY and REQUIRED arguments and set CSPTR_FOUND to TRUE if all listed variables are TRUE. +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(CSPTR DEFAULT_MSG CSPTR_LIBRARY CSPTR_INCLUDE_DIR) + +# Copy the results to the output variables. +IF(CSPTR_FOUND) + SET(CSPTR_LIBRARIES ${CSPTR_LIBRARY}) + SET(CSPTR_INCLUDE_DIRS ${CSPTR_INCLUDE_DIR}) +ELSE(CSPTR_FOUND) + SET(CSPTR_LIBRARIES) + SET(CSPTR_INCLUDE_DIRS) +ENDIF(CSPTR_FOUND) + +MARK_AS_ADVANCED(CSPTR_INCLUDE_DIRS CSPTR_LIBRARIES) diff --git a/.cmake/Modules/FindLibintl.cmake b/.cmake/Modules/FindLibintl.cmake new file mode 100644 index 0000000..90c9d85 --- /dev/null +++ b/.cmake/Modules/FindLibintl.cmake @@ -0,0 +1,62 @@ +# Try to find Libintl functionality +# Once done this will define +# +# LIBINTL_FOUND - system has Libintl +# LIBINTL_INCLUDE_DIR - Libintl include directory +# LIBINTL_LIBRARIES - Libraries needed to use Libintl +# +# TODO: This will enable translations only if Gettext functionality is +# present in libc. Must have more robust system for release, where Gettext +# functionality can also reside in standalone Gettext library, or the one +# embedded within kdelibs (cf. gettext.m4 from Gettext source). + +# Copyright (c) 2006, Chusslove Illich, +# Copyright (c) 2007, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +SET(LIBINTL_SEARCH_PATHS + /usr/local/opt/ + /usr/local/ + /usr/ +) + +if(LIBINTL_INCLUDE_DIR AND LIBINTL_LIB_FOUND) + set(Libintl_FIND_QUIETLY TRUE) +endif(LIBINTL_INCLUDE_DIR AND LIBINTL_LIB_FOUND) + +find_path(LIBINTL_INCLUDE_DIR libintl.h + HINTS + PATH_SUFFIXES gettext/include + PATHS ${LIBINTL_SEARCH_PATHS} +) + +set(LIBINTL_LIB_FOUND FALSE) + +if(LIBINTL_INCLUDE_DIR) + include(CheckSymbolExists) + check_symbol_exists(dgettext libintl.h LIBINTL_LIBC_HAS_DGETTEXT) + + if (LIBINTL_LIBC_HAS_DGETTEXT) + set(LIBINTL_LIBRARIES) + set(LIBINTL_LIB_FOUND TRUE) + else (LIBINTL_LIBC_HAS_DGETTEXT) + find_library(LIBINTL_LIBRARIES + NAMES intl + HINTS + PATH_SUFFIXES gettext/lib + PATHS ${LIBINTL_SEARCH_PATHS} + ) + if(LIBINTL_LIBRARIES) + set(LIBINTL_LIB_FOUND TRUE) + endif(LIBINTL_LIBRARIES) + endif (LIBINTL_LIBC_HAS_DGETTEXT) + +endif(LIBINTL_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Libintl DEFAULT_MSG LIBINTL_INCLUDE_DIR LIBINTL_LIB_FOUND) + +mark_as_advanced(LIBINTL_INCLUDE_DIR LIBINTL_LIBRARIES LIBINTL_LIBC_HAS_DGETTEXT LIBINTL_LIB_FOUND) diff --git a/.cmake/Modules/FindPCRE.cmake b/.cmake/Modules/FindPCRE.cmake new file mode 100644 index 0000000..dbbd60a --- /dev/null +++ b/.cmake/Modules/FindPCRE.cmake @@ -0,0 +1,37 @@ +# Copyright (C) 2007-2009 LuaDist. +# Created by Peter Kapec +# Redistribution and use of this file is allowed according to the terms of the MIT license. +# For details see the COPYRIGHT file distributed with LuaDist. +# Note: +# Searching headers and libraries is very simple and is NOT as powerful as scripts +# distributed with CMake, because LuaDist defines directories to search for. +# Everyone is encouraged to contact the author with improvements. Maybe this file +# becomes part of CMake distribution sometimes. + +# - Find pcre +# Find the native PCRE headers and libraries. +# +# PCRE_INCLUDE_DIRS - where to find pcre.h, etc. +# PCRE_LIBRARIES - List of libraries when using pcre. +# PCRE_FOUND - True if pcre found. + +# Look for the header file. +FIND_PATH(PCRE_INCLUDE_DIR NAMES pcre.h) + +# Look for the library. +FIND_LIBRARY(PCRE_LIBRARY NAMES pcre) + +# Handle the QUIETLY and REQUIRED arguments and set PCRE_FOUND to TRUE if all listed variables are TRUE. +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE DEFAULT_MSG PCRE_LIBRARY PCRE_INCLUDE_DIR) + +# Copy the results to the output variables. +IF(PCRE_FOUND) + SET(PCRE_LIBRARIES ${PCRE_LIBRARY}) + SET(PCRE_INCLUDE_DIRS ${PCRE_INCLUDE_DIR}) +ELSE(PCRE_FOUND) + SET(PCRE_LIBRARIES) + SET(PCRE_INCLUDE_DIRS) +ENDIF(PCRE_FOUND) + +MARK_AS_ADVANCED(PCRE_INCLUDE_DIRS PCRE_LIBRARIES) diff --git a/.cmake/Modules/GettextTranslate.cmake b/.cmake/Modules/GettextTranslate.cmake new file mode 100644 index 0000000..553b13d --- /dev/null +++ b/.cmake/Modules/GettextTranslate.cmake @@ -0,0 +1,285 @@ +# Copyright (c) 2012, Jarryd Beck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +# This module creates build rules for updating translation files made +# with gettext +# In your top level CMakeLists.txt, do +# include(GettextTranslate) +# then in any po directory where you want things to be translated, write +# GettextTranslate() +# +# This module also finds the gettext binaries. If these are in a non-standard +# location, you can define the following variables to provide paths to search +# in +# GettextTranslate_BINARIES --- a path in which to look for every program +# GettextTranslate_XGETTEXT --- the xgettext program +# GettextTranslate_MSGINIT --- the msginit program +# GettextTranslate_MSGFILTER --- the msgfilter program +# GettextTranslate_MSGCONV --- the msgconv program +# GettextTranslate_MSGMERGE --- the msgmerge program +# GettextTranslate_MSGFMT --- the msgfmt program +# these are searched first before $PATH, so set this if you have your own +# version that overrides the system version +# +# it reads variables from Makevars, one of the most important being DOMAIN +# it reads the languages to generate from LINGUAS +# +# it adds the following targets +# update-po +# update-gmo +# ${DOMAIN}-pot.update +# generate-${DOMAIN}-${lang}-po +# generate-${DOMAIN}-${lang}-gmo +# +# where ${DOMAIN} is the DOMAIN variable read from Makevars +# and ${lang} is each language mentioned in LINGUAS +# +# if you want update-gmo to be added to the "all" target, then define the +# variable GettextTranslate_ALL before including this file +# +# by default, the gmo files are built in the source directory. If you want +# them to be built in the binary directory, then define the variable +# GettextTranslate_GMO_BINARY + + + +# add the update-po and update-gmo targets, the actual files that need to +# depend on this will be added as we go + +if (DEFINED GettextTranslate_ALL) + set(_addToALL "ALL") +endif() + +add_custom_target(update-po) +add_custom_target(update-gmo ${_addToALL}) + +#look for all the programs +#xgettext, msginit, msgfilter, msgconv, msgmerge, msgfmt + +function(REQUIRE_BINARY binname varname) + if (defined ${${varname}-NOTFOUND}) + message(FATAL_ERROR "Could not find " binname) + endif() +endfunction() + +find_program(GettextTranslate_XGETTEXT_EXECUTABLE xgettext + HINTS ${GettextTranslate_XGETTEXT} ${GettextTranslate_BINARIES} +) +REQUIRE_BINARY(xgettext GettextTranslate_XGETTEXT_EXECUTABLE) + +find_program(GettextTranslate_MSGINIT_EXECUTABLE msginit + HINTS ${GettextTranslate_MSGINIT} ${GettextTranslate_BINARIES} +) +REQUIRE_BINARY(msginit GettextTranslate_MSGINIT_EXECUTABLE) + +find_program(GettextTranslate_MSGFILTER_EXECUTABLE msgfilter + HINTS ${GettextTranslate_MSGFILTER} ${GettextTranslate_BINARIES} +) +REQUIRE_BINARY(msgfilter GettextTranslate_MSGFILTER_EXECUTABLE) + +find_program(GettextTranslate_MSGCONV_EXECUTABLE msgconv + HINTS ${GettextTranslate_MSGCONV} ${GettextTranslate_BINARIES} +) +REQUIRE_BINARY(msgconv GettextTranslate_MSGCONV_EXECUTABLE) + +find_program(GettextTranslate_MSGMERGE_EXECUTABLE msgmerge + HINTS ${GettextTranslate_MSGMERGE} ${GettextTranslate_BINARIES} +) +REQUIRE_BINARY(msgmerge GettextTranslate_MSGMERGE_EXECUTABLE) + +find_program(GettextTranslate_MSGFMT_EXECUTABLE msgfmt + HINTS ${GettextTranslate_MSGFMT} ${GettextTranslate_BINARIES} +) +REQUIRE_BINARY(msgfmt GettextTranslate_MSGFMT_EXECUTABLE) + +mark_as_advanced( + GettextTranslate_MSGCONV_EXECUTABLE + GettextTranslate_MSGFILTER_EXECUTABLE + GettextTranslate_MSGFMT_EXECUTABLE + GettextTranslate_MSGINIT_EXECUTABLE + GettextTranslate_MSGMERGE_EXECUTABLE + GettextTranslate_XGETTEXT_EXECUTABLE +) + +macro(GettextTranslate) + + if(GettextTranslate_GMO_BINARY) + set (GMO_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) + else() + set (GMO_BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in) + message(FATAL_ERROR "There is no POTFILES.in in + ${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Makevars) + message(FATAL_ERROR "There is no Makevars in ${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/Makevars makevars + REGEX "^[^=]+=(.*)$" + ) + + foreach(makevar ${makevars}) + string(REGEX REPLACE "^([^= ]+) =[ ]?(.*)$" "\\1" MAKEVAR_KEY ${makevar}) + string(REGEX REPLACE "^([^= ]+) =[ ]?(.*)$" "\\2" + MAKEVAR_${MAKEVAR_KEY} ${makevar}) + endforeach() + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in + ${CMAKE_CURRENT_BINARY_DIR}/POTFILES + COPYONLY + ) + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LINGUAS + ${CMAKE_CURRENT_BINARY_DIR}/LINGUAS + COPYONLY + ) + + #set the directory to not clean + #set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + # PROPERTY CLEAN_NO_CUSTOM true) + + file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in potfiles + REGEX "^[^#].*" + ) + + foreach(potfile ${potfiles}) + list(APPEND source_translatable + ${CMAKE_CURRENT_SOURCE_DIR}/${MAKEVAR_top_builddir}/${potfile}) + endforeach() + + set(TEMPLATE_FILE ${MAKEVAR_DOMAIN}.pot) + set(TEMPLATE_FILE_ABS ${CMAKE_CURRENT_SOURCE_DIR}/${TEMPLATE_FILE}) + string(REGEX MATCHALL "[^ ]+" XGETTEXT_OPTS ${MAKEVAR_XGETTEXT_OPTIONS}) + #add_custom_target(${MAKEVAR_DOMAIN}.pot-update DEPENDS + # ${TEMPLATE_FILE_ABS} + #) + + add_custom_target(${MAKEVAR_DOMAIN}.pot-update + COMMAND ${GettextTranslate_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTS} + -o ${TEMPLATE_FILE_ABS} + --default-domain=${MAKEVAR_DOMAIN} + --add-comments=TRANSLATORS: + --copyright-holder=${MAKEVAR_COPYRIGHT_HOLDER} + --msgid-bugs-address="${MAKEVAR_MSGID_BUGS_ADDRESS}" + --directory=${MAKEVAR_top_builddir} + --files-from=${CMAKE_CURRENT_BINARY_DIR}/POTFILES + --package-version=${VERSION} + --package-name=${CMAKE_PROJECT_NAME} + DEPENDS ${source_translatable} + ${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + + #add_custom_command(OUTPUT ${TEMPLATE_FILE_ABS} + # COMMAND ${GettextTranslate_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTS} + # -o ${TEMPLATE_FILE_ABS} + # --default-domain=${MAKEVAR_DOMAIN} + # --add-comments=TRANSLATORS: + # --copyright-holder=${MAKEVAR_COPYRIGHT_HOLDER} + # --msgid-bugs-address="${MAKEVAR_MSGID_BUGS_ADDRESS}" + # --directory=${MAKEVAR_top_builddir} + # --files-from=${CMAKE_CURRENT_BINARY_DIR}/POTFILES + # --package-version=${VERSION} + # --package-name=${CMAKE_PROJECT_NAME} + # DEPENDS ${source_translatable} + # ${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in + # WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + #) + + #add_dependencies(update-po ${MAKEVAR_DOMAIN}.pot-update) + + file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/LINGUAS LINGUAS + REGEX "^[^#].*") + string(REGEX MATCHALL "[^ ]+" languages ${LINGUAS}) + + foreach(lang ${languages}) + set(PO_FILE_NAME "${CMAKE_CURRENT_SOURCE_DIR}/${lang}.po") + set(GMO_FILE_NAME "${GMO_BUILD_DIR}/${lang}.gmo") + set(PO_TARGET "generate-${MAKEVAR_DOMAIN}-${lang}-po") + set(GMO_TARGET "generate-${MAKEVAR_DOMAIN}-${lang}-gmo") + list(APPEND po_files ${PO_TARGET}) + list(APPEND gmo_files ${GMO_TARGET}) + + if(${lang} MATCHES "en@(.*)quot") + + add_custom_command(OUTPUT ${lang}.insert-header + COMMAND + sed -e "'/^#/d'" -e 's/HEADER/${lang}.header/g' + ${CMAKE_CURRENT_SOURCE_DIR}/insert-header.sin > ${lang}.insert-header + ) + + #generate the en@quot files + add_custom_target(${PO_TARGET} + COMMAND + ${GettextTranslate_MSGINIT_EXECUTABLE} -i ${TEMPLATE_FILE_ABS} + --no-translator -l ${lang} + -o - 2>/dev/null + | sed -f ${CMAKE_CURRENT_BINARY_DIR}/${lang}.insert-header + | ${GettextTranslate_MSGCONV_EXECUTABLE} -t UTF-8 + | ${GettextTranslate_MSGFILTER_EXECUTABLE} sed -f + ${CMAKE_CURRENT_SOURCE_DIR}/`echo ${lang} + | sed -e 's/.*@//'`.sed 2>/dev/null > + ${PO_FILE_NAME} + DEPENDS ${lang}.insert-header ${TEMPLATE_FILE_ABS} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + + else() + + add_custom_target(${PO_TARGET} + COMMAND ${GettextTranslate_MSGMERGE_EXECUTABLE} --lang=${lang} + ${PO_FILE_NAME} ${TEMPLATE_FILE_ABS} + -o ${PO_FILE_NAME}.new + COMMAND mv ${PO_FILE_NAME}.new ${PO_FILE_NAME} + DEPENDS ${TEMPLATE_FILE_ABS} + ) + + endif() + + add_custom_target(${GMO_TARGET} + COMMAND ${GettextTranslate_MSGFMT_EXECUTABLE} -c --statistics --verbose + -o ${GMO_FILE_NAME} ${PO_FILE_NAME} + DEPENDS ${PO_TARGET} + ) + + add_dependencies(${PO_TARGET} ${MAKEVAR_DOMAIN}.pot-update) + + install(FILES ${GMO_FILE_NAME} DESTINATION + ${LOCALEDIR}/${lang}/LC_MESSAGES + RENAME ${MAKEVAR_DOMAIN}.mo + ) + + endforeach() + + add_dependencies(update-po ${po_files}) + add_dependencies(update-gmo ${gmo_files}) + +#string(REGEX MATCH "^[^=]+=(.*)$" parsed_variables ${makevars}) + +endmacro() diff --git a/.cmake/Modules/uninstall.cmake b/.cmake/Modules/uninstall.cmake new file mode 100644 index 0000000..e5ed88e --- /dev/null +++ b/.cmake/Modules/uninstall.cmake @@ -0,0 +1,23 @@ +set(MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt") + +if(NOT EXISTS ${MANIFEST}) +message(FATAL_ERROR "Cannot find install manifest: '${MANIFEST}'") +endif() + +file(STRINGS ${MANIFEST} files) +foreach(file ${files}) +if(EXISTS ${file}) +message(STATUS "Removing file: '${file}'") + +exec_program( +${CMAKE_COMMAND} ARGS "-E remove ${file}" +OUTPUT_VARIABLE stdout +RETURN_VALUE result +) +if(NOT "${result}" STREQUAL 0) +message(FATAL_ERROR "Failed to remove file: '${file}'.") +endif() +else() +MESSAGE(STATUS "File '${file}' does not exist.") +endif() +endforeach(file) diff --git a/.gitignore b/.gitignore index f9e0c45..48d1edb 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,12 @@ !HEADER !ChangeLog -!Makefile.am -!configure.ac -!autogen.sh +!CMakeLists.txt +!.cmake/* +!src/config.h.in src/config.h +build *~ *.swp diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index fb15be4..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "dependencies/csptr"] - path = dependencies/csptr - url = https://github.com/Snaipe/c-smart-pointers.git diff --git a/.travis.yml b/.travis.yml index 1624538..010eab6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,24 @@ language: c compiler: - - gcc +- gcc +sudo: false +addons: + apt: + packages: + - check + - gettext + - cmake before_install: - - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - - sudo apt-get -qq update - - sudo apt-get -qq install -y check gcc-4.9 gettext autopoint - - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 90 - - sudo pip install cpp-coveralls +- export LOCAL_INSTALL="$HOME" +- ".ci/install-libcsptr.sh" +- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/lib +- export CFLAGS="-g -O0" script: - - ./autogen.sh && ./configure --enable-gcov CFLAGS="-g -O0" && make && make -C samples check +- mkdir -p build && cd $_ && cmake -DCOVERALLS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=$HOME .. && make && make test after_success: - - coveralls --gcov gcov-4.9 --exclude samples --exclude dependencies --gcov-options '\-lp' -b . +- make coveralls after_failure: - - cat $(find samples -iname '*.log') /dev/null +- cat Testing/Temporary/LastTest.log +env: + global: + secure: bzZcWjdqoTgceC40kEBucx7NuWYJPk+rxgF3UJJDXi+ijQAFYPv70p5eVsGR6rfc+XgqXCxcUFQtuL4ZVt7QEfVk1ZOJITNeHbKIeKaEYS4nX8mFf+CBeEm9bJGZ04KiQJdJu5mzzAHvXbW7roGXDGWe1Bjnk5wwA+dNUCa7H04= diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..da1994c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,132 @@ +cmake_minimum_required(VERSION 2.8) + +project(Criterion C) + +# Project setup & environment variables + +enable_testing() +add_subdirectory(samples) + +set(PROJECT_VERSION "1.3.0") +set(LOCALEDIR ${CMAKE_INSTALL_PREFIX}/share/locale) +set(GettextTranslate_ALL) +set(GettextTranslate_GMO_BINARY) +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") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-no-undefined") + +# Setup coveralls + +option(COVERALLS "Turn on coveralls support" OFF) +option(COVERALLS_UPLOAD "Upload the generated coveralls json" ON) + +if (COVERALLS) + include(Coveralls) + coveralls_turn_on_coverage() +endif() + +# Find dependencies + +find_package(Gettext) +find_package(Libintl) +if (GETTEXT_FOUND AND LIBINTL_LIB_FOUND) + include(GettextTranslate) + add_subdirectory(po) + set(ENABLE_NLS 1) +endif () + +include(CheckLibraryExists) +CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME) + +find_package(PCRE) +find_package(Libcsptr REQUIRED) + +# List sources and headers + +set(SOURCE_FILES + src/abort.c + src/abort.h + src/event.c + src/event.h + src/report.c + src/report.h + src/runner.c + src/runner.h + src/process.c + src/process.h + src/stats.c + src/stats.h + src/log/logging.c + src/log/tap.c + src/log/normal.c + src/options.c + src/timer.c + src/timer.h + src/i18n.c + src/i18n.h + src/ordered-set.c + src/posix-compat.c + src/main.c +) + +if (PCRE_FOUND) + set (SOURCE_FILES ${SOURCE_FILES} + src/extmatch.c + src/extmatch.h + ) + set(HAVE_PCRE 1) +endif () + +set(INTERFACE_FILES + include/criterion/assert.h + include/criterion/abort.h + include/criterion/common.h + include/criterion/criterion.h + include/criterion/event.h + include/criterion/hooks.h + include/criterion/logging.h + include/criterion/types.h + include/criterion/options.h + include/criterion/ordered-set.h + include/criterion/stats.h +) + +# Generate the configure file + +configure_file( + "${CMAKE_SOURCE_DIR}/src/config.h.in" + "${CMAKE_SOURCE_DIR}/src/config.h" +) + +include_directories(include src ${CSPTR_INCLUDE_DIRS}) +add_library(criterion SHARED ${SOURCE_FILES} ${INTERFACE_FILES}) +target_link_libraries(criterion ${CSPTR_LIBRARIES}) + +if (HAVE_CLOCK_GETTIME) + target_link_libraries(criterion rt) +endif() + +if (PCRE_FOUND) + target_link_libraries(criterion ${PCRE_LIBRARIES}) +endif() + +if (LIBINTL_LIB_FOUND) + target_link_libraries(criterion ${LIBINTL_LIBRARIES}) +endif() + +if (COVERALLS) + coveralls_setup("${SOURCE_FILES}" ${COVERALLS_UPLOAD}) +endif() + +install(FILES ${INTERFACE_FILES} DESTINATION include/criterion) +install(TARGETS criterion + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +add_custom_target(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_MODULE_PATH}/uninstall.cmake" +) diff --git a/ChangeLog b/ChangeLog index 16e76e0..afba940 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2015-08-05 Franklin "Snaipe" Mathieu + + * criterion: version 1.3.0 + * Turned the library into a shared library. + * Added extended globbing for --pattern (requires PCRE) + * Switched to a CMake build system + * Fixed windows builds & output + * Added basic windows SEH-to-signal translator + 2015-04-26 Franklin "Snaipe" Mathieu * criterion: version 1.2.1 diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 87b4321..0000000 --- a/Makefile.am +++ /dev/null @@ -1,76 +0,0 @@ -ACLOCAL_AMFLAGS = -I m4 -AM_CPPFLAGS = -DLOCALEDIR='"$(localedir)"' -SUBDIRS = po dependencies/csptr samples - -lib_LTLIBRARIES = libcriterion.la - -WARNINGS = -Wall -Wextra \ - -Wno-unused-result - -libcriterion_la_CFLAGS = \ - $(WARNINGS) \ - -std=gnu11 \ - -I$(top_srcdir)/src/ \ - -I$(top_srcdir)/include/ \ - -I$(top_srcdir)/dependencies/csptr/include/ \ - $(COVERAGE_CFLAGS) - -libcriterion_la_LDFLAGS = $(COVERAGE_LDFLAGS) -version-info 1:0:0 - -# dirty but unless someone has a better alternative... -libcriterion_la_LIBADD = dependencies/csptr/src/libcsptr_la-*.lo - -EXTRA_DIST = config.rpath LICENSE - -subdirincludedir = $(includedir)/criterion/ -subdirinclude_HEADERS = \ - include/criterion/assert.h \ - include/criterion/abort.h \ - include/criterion/common.h \ - include/criterion/criterion.h \ - include/criterion/event.h \ - include/criterion/hooks.h \ - include/criterion/logging.h \ - include/criterion/types.h \ - include/criterion/options.h \ - include/criterion/ordered-set.h \ - include/criterion/stats.h - -libcriterion_la_SOURCES = \ - src/abort.c \ - src/abort.h \ - src/event.c \ - src/event.h \ - src/report.c \ - src/report.h \ - src/runner.c \ - src/runner.h \ - src/process.c \ - src/process.h \ - src/stats.c \ - src/stats.h \ - src/log/logging.c \ - src/log/tap.c \ - src/log/normal.c \ - src/options.c \ - src/timer.c \ - src/timer.h \ - src/i18n.c \ - src/i18n.h \ - src/ordered-set.c \ - src/posix-compat.c \ - src/main.c - -TARGET = $(PACKAGE)-$(VERSION) - -package: all - rm -Rf $(TARGET) - mkdir -p $(TARGET) - cp -Rf .libs $(TARGET)/lib/ - rm -f $(TARGET)/lib/libcriterion.la - cp -f libcriterion.la $(TARGET)/lib - cp -Rf include $(TARGET) - tar -cvjf $(TARGET).tar.bz2 $(TARGET) - -clean-local: - rm -Rf $(TARGET) $(TARGET).tar.bz2 diff --git a/README.md b/README.md index 5974341..ac6f1e7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Criterion +Criterion Logo ========= [![Build Status](https://travis-ci.org/Snaipe/Criterion.svg?branch=bleeding)](https://travis-ci.org/Snaipe/Criterion) @@ -31,14 +31,14 @@ the user would have with other frameworks: reported and tested. * [x] Progress and statistics can be followed in real time with report hooks. * [x] TAP output format can be enabled with an option. -* [x] Runs on Linux, FreeBSD, Mac OS X, and Windows (Compiles only with MinGW or Cygwin). +* [x] Runs on Linux, FreeBSD, Mac OS X, and Windows (Compiling with MinGW GCC). ## Downloads -* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-linux-x86_64.tar.bz2) -* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-osx-x86_64.tar.bz2) -* [Windows (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-win-x86_64.tar.bz2) -* [FreeBSD (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.2.0/criterion-1.2.0-freebsd-x86_64.tar.bz2) +* [Linux (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.0/criterion-1.3.0-linux-x86_64.tar.bz2) +* [OS X (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.0/criterion-1.3.0-osx-x86_64.tar.bz2) +* [FreeBSD (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.0/criterion-1.3.0-freebsd-x86_64.tar.bz2) +* [Windows (x86_64)](https://github.com/Snaipe/Criterion/releases/download/v1.3.0/criterion-1.3.0-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) @@ -99,8 +99,11 @@ A. I worked with CUnit and Check, and I must say that they do their job **Q. Where has this been tested?** A. Currently, on Linux 2.6.32 and Linux 3.15.7, although it should work on - most \*nix systems; Mac OS X Yosemite 10.10, FreeBSD 10.0, and finally - Windows 7 (with the MinGW and Cygwin ports of GCC). + most \*nix systems; Mac OS X Yosemite 10.10, FreeBSD 10.0, Windows 7 and 2K. + +## Credits + +Logo done by [Greehm](http://www.cargocollective.com/pbouigue) [online-docs]: http://criterion.readthedocs.org/ [pdf-docs]: http://readthedocs.org/projects/criterion/downloads/pdf/latest/ diff --git a/appveyor.yml b/appveyor.yml index 984993d..ae1ca2b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,46 +1,15 @@ -version: 1.2.2_b{build}-{branch} +version: 1.3.0_b{build}-{branch} os: Windows Server 2012 init: - - ps: (New-Object System.Net.WebClient).DownloadFile("https://cygwin.com/setup-x86.exe", "c:\setup-x86.exe") - git config --global core.autocrlf input - - c:\setup-x86.exe -qnNdO -R %CYG_ROOT% -s %CYG_MIRROR% -l %CYG_CACHE% \ - -P autoconf \ - -P automake \ - -P gcc-core \ - -P mingw-runtime \ - -P mingw-binutils \ - -P mingw-gcc-core \ - -P mingw-pthreads \ - -P mingw-w32api \ - -P mingw64-i686-gcc-core \ - -P libtool \ - -P make \ - -P python3 \ - -P gettext-devel \ - -P gettext \ - -P expat \ - -P intltool \ - -P libiconv \ - -P pkg-config \ - -P check \ - -P git \ - -P wget \ - -P curl + - 'SET PATH=%PATH%;C:\MinGW\msys\1.0\bin;C:\MinGW\bin;%APPVEYOR_BUILD_FOLDER%\bin;%APPVEYOR_BUILD_FOLDER%\build' environment: - global: - CYG_ROOT: C:\cygwin - CYG_MIRROR: http://cygwin.mirror.constant.com - CYG_CACHE: C:\cygwin\var\cache\setup - CYG_BASH: C:\cygwin\bin\bash - COVERALLS_TOKEN: + COVERALLS_REPO_TOKEN: secure: 5nuCg+faxFPeppoNNcSwVobswAVFUf8ut83vw8CX/4W2y0kZkGmwEfCUxSQWiQDU -cache: - - '%CYG_CACHE%' - clone_depth: 5 matrix: @@ -53,22 +22,19 @@ configuration: Release install: - 'set GCOV_PREFIX=%APPVEYOR_BUILD_FOLDER%' - - "%CYG_BASH% -lc 'cd $APPVEYOR_BUILD_FOLDER; ./autogen.sh'" - - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 # include +# include "common.h" struct criterion_test_extra_data { int sentinel_; diff --git a/m4/.keep b/m4/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt new file mode 100644 index 0000000..892654a --- /dev/null +++ b/po/CMakeLists.txt @@ -0,0 +1 @@ +GettextTranslate() diff --git a/po/Makevars b/po/Makevars index 70daf30..036417c 100644 --- a/po/Makevars +++ b/po/Makevars @@ -1,7 +1,7 @@ # Makefile variables for PO directory in any package using GNU gettext. # Usually the message domain is the same as the package name. -DOMAIN = $(PACKAGE) +DOMAIN = Criterion # These two variables depend on the location of this directory. subdir = po diff --git a/po/fr.po b/po/fr.po index ffbd217..7727e40 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: \n" -"POT-Creation-Date: 2015-04-10 23:08+0200\n" +"POT-Creation-Date: 2015-07-28 22:17+0200\n" "PO-Revision-Date: 2015-04-03 17:58+0200\n" "Last-Translator: \n" "Language-Team: French\n" @@ -70,8 +70,10 @@ msgid "%1$s::%2$s: CRASH!\n" msgstr "%1$s::%2$s: PLANTAGE!\n" #: src/log/normal.c:139 -#, c-format -msgid "%1$sWarning! This test crashed during its setup or teardown.%2$s\n" +#, fuzzy, c-format +msgid "" +"%1$sWarning! The test `%2$s::%3$s` crashed during its setup or teardown." +"%4$s\n" msgstr "" -"%1$sAttention! Ce test a planté pendant son initialisation ou sa " -"finalisation.%2$s\n" +"%1$sAttention! Le test `%2$s::%3$s` a planté pendant son initialisation ou " +"sa finalisation.%4$s\n" diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt new file mode 100644 index 0000000..206c049 --- /dev/null +++ b/samples/CMakeLists.txt @@ -0,0 +1,44 @@ +project(criterion_samples) + +set(CMAKE_C_FLAGS "-std=c99 -Wall -Wextra -pedantic") + +include_directories(../include) + +set(SAMPLES + signal + report + suites + fixtures + asserts + more-suites + long-messages + description + other-crashes + simple +) + +set(SCRIPTS + tap_test + 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" + ) +endforeach() + +foreach(script ${SCRIPTS}) + add_test(${script} sh ${CMAKE_CURRENT_LIST_DIR}/tests/${script}.sh) + set_property(TEST ${script} PROPERTY + ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1" + ) +endforeach() diff --git a/samples/signal.c b/samples/signal.c index 1cda5d9..9dcc99d 100644 --- a/samples/signal.c +++ b/samples/signal.c @@ -8,8 +8,9 @@ Test(simple, caught, .signal = SIGSEGV) { *i = 42; } -Test(simple, wrong_signal, .signal = SIGSEGV) { - raise(SIGINT); +Test(simple, wrong_signal, .signal = SIGINT) { + int *i = NULL; + *i = 42; } Test(simple, uncaught) { diff --git a/samples/tests/pattern.sh b/samples/tests/pattern.sh index c44e812..09996f1 100755 --- a/samples/tests/pattern.sh +++ b/samples/tests/pattern.sh @@ -1,2 +1,10 @@ -#!/bin/sh +#!/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' diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..5928b0b --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,11 @@ +#ifndef CONFIG_H_IN_ +# define CONFIG_H_IN_ + +#cmakedefine ENABLE_NLS @ENABLE_NLS@ +#cmakedefine HAVE_PCRE @HAVE_PCRE@ + +# define LOCALEDIR "${LOCALEDIR}" +# define PACKAGE "${PROJECT_NAME}" +# define VERSION "${PROJECT_VERSION}" + +#endif /* !CONFIG_H_IN_ */ diff --git a/src/event.c b/src/event.c index cca6ce6..6146c95 100644 --- a/src/event.c +++ b/src/event.c @@ -45,8 +45,10 @@ struct event *read_event(FILE *f) { 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) + if (fread(buf, assert_size, 1, f) == 0) { + free(buf); return NULL; + } return unique_ptr(struct event, .value = { .kind = kind, .data = buf }, @@ -54,8 +56,10 @@ struct event *read_event(FILE *f) { } case POST_TEST: { double *elapsed_time = malloc(sizeof (double)); - if (fread(elapsed_time, sizeof (double), 1, f) == 0) + if (fread(elapsed_time, sizeof (double), 1, f) == 0) { + free(elapsed_time); return NULL; + } return unique_ptr(struct event, .value = { .kind = kind, .data = elapsed_time }, diff --git a/src/extmatch.c b/src/extmatch.c new file mode 100644 index 0000000..205a148 --- /dev/null +++ b/src/extmatch.c @@ -0,0 +1,261 @@ +#include +#include +#include +#include + +#include +#include "criterion/common.h" + +struct context { + int depth; + char *dst; + const char *src; + char old, cur; + int eos; + const char **errmsg; + jmp_buf *jmp; +}; + +void transform_impl(struct context *ctx); + +static inline void transform_rec(struct context *ctx) { + struct context new_ctx = { + .depth = ctx->depth + 1, + .dst = ctx->dst, + .src = ctx->src, + .old = ctx->old, + .eos = ctx->eos, + .errmsg = ctx->errmsg, + .jmp = ctx->jmp, + }; + transform_impl(&new_ctx); + ctx->dst = new_ctx.dst; + ctx->src = new_ctx.src; + ctx->old = new_ctx.old; + ctx->eos = new_ctx.eos; +} + +static inline char read_char(struct context *ctx) { + char c = *ctx->src; + ctx->old = ctx->cur; + ctx->cur = c; + if (c == '\0') + ctx->eos = 1; + ++ctx->src; + return c; +} + +static inline char peek_char(struct context *ctx) { + return *ctx->src; +} + +static inline void copy_char(struct context *ctx, char src) { + *ctx->dst = src; + ++ctx->dst; +} + +static inline void dup_char(struct context *ctx) { + copy_char(ctx, read_char(ctx)); +} + +static inline void copy_str(struct context *ctx, const char *src) { + size_t len = strlen(src); + strncpy(ctx->dst, src, len); + ctx->dst += len; +} + +#define PREPREFIX 0 +#define POSTPREFIX 1 +#define PRESUFFIX 2 +#define POSTSUFFIX 3 +#define ELSESTR 4 + +typedef struct { + int (*validator)(struct context *ctx); + char *str; +} handler_arg; + +static int active() { return 1; } +static int inactive() { return 0; } + +static int is_eos(struct context *ctx) { + return peek_char(ctx) == '\0'; +} + +static inline void handle_special(struct context *ctx, handler_arg strs[5]) { + if (peek_char(ctx) == '(') { + if ((strs[0].validator ?: inactive)(ctx)) + copy_str(ctx, strs[0].str); + dup_char(ctx); + if ((strs[1].validator ?: inactive)(ctx)) + copy_str(ctx, strs[1].str); + + transform_rec(ctx); + + if ((strs[2].validator ?: inactive)(ctx)) + copy_str(ctx,strs[2].str); + copy_char(ctx, ')'); + if ((strs[3].validator ?: inactive)(ctx)) + copy_str(ctx, strs[3].str); + } else if ((strs[4].validator ?: inactive)(ctx)) { + copy_str(ctx, strs[4].str); + } +} + +# define Handler(Name, ...) \ + static void Name(struct context *ctx, UNUSED char c) { \ + handle_special(ctx, (handler_arg[5]) { \ + __VA_ARGS__ \ + }); \ + } + +# define _ active +Handler(handle_plus, [POSTSUFFIX] = {_, "+"}, [ELSESTR] = {_, "+" }); +Handler(handle_star, [POSTSUFFIX] = {_, "*"}, [ELSESTR] = {_, ".*"}); +Handler(handle_wild, [POSTSUFFIX] = {_, "?"}, [ELSESTR] = {_, "." }); +Handler(handle_excl, + [POSTPREFIX] = {_, "?!"}, + [PRESUFFIX] = {is_eos, "$" }, + [POSTSUFFIX] = {_, ".*"}, + [ELSESTR] = {_, "!" } + ); +Handler(handle_at, [ELSESTR] = {_, "@"}); +# undef _ + +static void handle_rbra(struct context *ctx, UNUSED char c) { + copy_char(ctx, c); + if (peek_char(ctx) == '!') { + read_char(ctx); + copy_char(ctx, '^'); + } +} + +static void escape_char(struct context *ctx, char c) { + copy_char(ctx, '\\'); + copy_char(ctx, c); +} + +static void escape_pipe(struct context *ctx, UNUSED char c) { + if (ctx->depth == 0) + copy_char(ctx, '\\'); + copy_char(ctx, '|'); +} + +typedef void (*f_handler)(struct context *, char); + +void transform_impl(struct context *ctx) { + static f_handler handlers[] = { + ['+'] = handle_plus, + ['*'] = handle_star, + ['?'] = handle_wild, + ['!'] = handle_excl, + ['['] = handle_rbra, + ['@'] = handle_at, + + ['.'] = escape_char, + ['('] = escape_char, + [')'] = escape_char, + ['|'] = escape_pipe, + + [255] = NULL, + }; + for (char c = read_char(ctx); !ctx->eos; c = read_char(ctx)) { + f_handler handler = handlers[(unsigned char) c]; + + if (ctx->old == '\\') + handler = copy_char; + + if (c == ')' && ctx->depth > 0) + return; + + (handler ?: copy_char)(ctx, c); + + if (ctx->eos) + return; + } + if (ctx->depth > 0) { + *ctx->errmsg = "mismatching parenthesis"; + longjmp(*ctx->jmp, -1); // abort operation + } +} + +static int transform(const char *pattern, char *result, const char **errmsg) { + jmp_buf jmp; + struct context ctx = { + .src = pattern, + .dst = result, + .errmsg = errmsg, + .jmp = &jmp, + }; + if (!setjmp(*ctx.jmp)) { + copy_char(&ctx, '^'); + transform_impl(&ctx); + copy_char(&ctx, '$'); + copy_char(&ctx, '\0'); + return 0; + } + return -1; +} + +/* + * let T be the transformation function, + * let diff be the function that yields the greatest character difference + * before and after its parameter has been transformed by T. + * + * T('*') = '.*'; diff('*') = 1 + * T('!()') = '(?!).*' or '(?!$).*'; diff('!()') = 4 + * T('@') = '' or '@'; diff('@') = 0 + * T('|') = '|' or '\|'; diff('|') = 1 + * T('.') = '\.'; diff('.') = 1 + * T('(') = '\('; diff('(') = 1 + * T(')') = '\)'; diff(')') = 1 + * for every other 1 character string s, we have T(s) = s; hence diff(s) = 0 + * + * let num be the function that yields the number of occurences of a string. + * let spec be the set {(s, num(s)) | ∀s} + * ∀s, lenght(T(s)) = length(s) + Σ((c_i, n_i) ∈ spec, n_i * diff(c_i)) + * + * let S = {'*', '!()', '|', '.', '(', ')'}. + * since ∀s ∉ S, diff(s) = 0, we can simplify the above equation as: + * + * ∀s, lenght(T(s)) = length(s) + num('*') + num('|') + num('.') + * + num('(') + num(')') + 4 * num('!()'). + * + * 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, + * + * let N = floor(length(u) / 3), + * let Q = length(u) mod 3, + * hence num('!()') = N. + * + * ∀s | lenght(s) = length(u), + * length(T(s)) <= length(T(u)) + * <= length(u) | 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(s) / 3) + 4 + * + */ +static inline size_t max_length(size_t len) { + return 7 * len / 3 + 4; +} + +int extmatch(const char *pattern, const char *string, const char **errmsg) { + char regex[max_length(strlen(pattern))]; + if (transform(pattern, regex, errmsg) != -1) { + int erroffset; + pcre *preg = pcre_compile(regex, 0, errmsg, &erroffset, NULL); + if (preg) { + int res = pcre_exec(preg, NULL, string, strlen(string), 0, 0, NULL, 0); + pcre_free(preg); + return res; + } + } + return -10; +} diff --git a/src/extmatch.h b/src/extmatch.h new file mode 100644 index 0000000..d65eeb1 --- /dev/null +++ b/src/extmatch.h @@ -0,0 +1,6 @@ +#ifndef EXTMATCH_H_ +# define EXTMATCH_H_ + +int extmatch(const char *pattern, const char *string, const char **errmsg); + +#endif /* !EXTMATCH_H_ */ diff --git a/src/log/logging.c b/src/log/logging.c index 5d2bc8d..b527e71 100644 --- a/src/log/logging.c +++ b/src/log/logging.c @@ -29,7 +29,11 @@ #include "criterion/options.h" #include "i18n.h" -#define LOG_FORMAT "[%1$s%2$s%3$s] %4$s" +#ifdef ENABLE_NLS +# define LOG_FORMAT "[%1$s%2$s%3$s] %4$s" +#else +# define LOG_FORMAT "[%s%s%s] %s" +#endif const struct criterion_prefix_data g_criterion_logging_prefixes[] = { [CRITERION_LOGGING_PREFIX_DASHES] = { "----", CRIT_FG_BLUE }, @@ -38,7 +42,7 @@ const struct criterion_prefix_data g_criterion_logging_prefixes[] = { [CRITERION_LOGGING_PREFIX_SKIP] = { "SKIP", CRIT_FG_GOLD }, [CRITERION_LOGGING_PREFIX_PASS] = { "PASS", CRIT_FG_GREEN }, [CRITERION_LOGGING_PREFIX_FAIL] = { "FAIL", CRIT_FG_RED }, - { NULL } + { NULL, NULL } }; void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...) { diff --git a/src/log/normal.c b/src/log/normal.c index 42645be..ff83888 100644 --- a/src/log/normal.c +++ b/src/log/normal.c @@ -33,24 +33,72 @@ #include "timer.h" #include "config.h" #include "i18n.h" +#include "posix-compat.h" + +#define USED __attribute__ ((used)) + +#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; + +static msg_t msg_pre_all = "Criterion v%s\n"; +static msg_t msg_desc = " %s\n"; + +#ifdef ENABLE_NLS +static msg_t msg_pre_init = "%1$s::%2$s\n"; +static msg_t msg_post_test_timed = "%1$s::%2$s: (%3$3.2fs)\n"; +static msg_t msg_post_test = "%1$s::%2$s\n"; +static msg_t msg_post_suite_test = "%1$s::%2$s: Test is disabled\n"; +static msg_t msg_post_suite_suite = "%1$s::%2$s: Suite is disabled\n"; +static msg_t msg_assert_fail = "%1$s%2$s%3$s:%4$s%5$d%6$s: Assertion failed: %7$s\n"; +static msg_t msg_test_crash_line = "%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 = "%1$s::%2$s: CRASH!\n"; +static msg_t msg_test_other_crash = "%1$sWarning! The test `%2$s::%3$s` crashed during its setup or teardown.%4$s\n"; +static msg_t msg_pre_suite = "Running %1$s%2$lu%3$s test from %4$s%5$s%6$s:\n"; +static msg_t msg_pre_suite_pl = "Running %1$s%2$lu%3$s tests from %4$s%5$s%6$s:\n"; +static msg_t msg_post_all = "%1$sSynthesis: Tested: %2$s%3$lu%4$s " + "| Passing: %5$s%6$lu%7$s " + "| Failing: %8$s%9$lu%10$s " + "| Crashing: %11$s%12$lu%13$s " + "%14$s\n"; +#else +static msg_t msg_pre_init = "%s::%s\n"; +static msg_t msg_post_test_timed = "%s::%s: (%3.2fs)\n"; +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_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_pre_suite = "Running %s%lu%s test from %s%s%s:\n"; +static msg_t msg_pre_suite_pl = "Running %s%lu%s tests from %s%s%s:\n"; +static msg_t msg_post_all = "%sSynthesis: Tested: %s%lu%s " + "| Passing: %s%lu%s " + "| Failing: %s%lu%s " + "| Crashing: %s%lu%s " + "%s\n"; +#endif void normal_log_pre_all(UNUSED struct criterion_test_set *set) { - criterion_pinfo(CRITERION_PREFIX_DASHES, _("Criterion v%s\n"), VERSION); + criterion_pinfo(CRITERION_PREFIX_DASHES, _(msg_pre_all), VERSION); } void normal_log_pre_init(struct criterion_test *test) { - criterion_pinfo(CRITERION_PREFIX_RUN, _("%1$s::%2$s\n"), + criterion_pinfo(CRITERION_PREFIX_RUN, _(msg_pre_init), test->category, test->name); if (test->data->description) - criterion_pinfo(CRITERION_PREFIX_RUN, _(" %s\n"), + criterion_pinfo(CRITERION_PREFIX_RUN, _(msg_desc), test->data->description); } void normal_log_post_test(struct criterion_test_stats *stats) { - const char *format = can_measure_time() ? "%1$s::%2$s: (%3$3.2fs)\n" - : "%1$s::%2$s\n"; + const char *format = can_measure_time() ? msg_post_test_timed : msg_post_test; const enum criterion_logging_level level = stats->failed ? CRITERION_IMPORTANT : CRITERION_INFO; @@ -73,15 +121,15 @@ void normal_log_post_suite(struct criterion_suite_stats *stats) { for (struct criterion_test_stats *ts = stats->tests; ts; ts = ts->next) { if (is_disabled(ts->test, stats->suite)) { const char *format = ts->test->data->disabled - ? _("%1$s::%2$s: Test is disabled\n") - : _("%1$s::%2$s: Suite is disabled\n"); + ? _(msg_post_suite_test) + : _(msg_post_suite_suite); criterion_pinfo(CRITERION_PREFIX_SKIP, format, ts->test->category, ts->test->name); if (ts->test->data->description) - criterion_pinfo(CRITERION_PREFIX_DASHES, " %s\n", + criterion_pinfo(CRITERION_PREFIX_DASHES, msg_desc, ts->test->data->description); } } @@ -91,11 +139,7 @@ void normal_log_post_all(struct criterion_global_stats *stats) { size_t tested = stats->nb_tests - stats->tests_skipped; criterion_pimportant(CRITERION_PREFIX_EQUALS, - _("%1$sSynthesis: Tested: %2$s%3$lu%4$s " - "| Passing: %5$s%6$lu%7$s " - "| Failing: %8$s%9$lu%10$s " - "| Crashing: %11$s%12$lu%13$s " - "%14$s\n"), + _(msg_post_all), FG_BOLD, FG_BLUE, (unsigned long) tested, FG_BOLD, FG_GREEN, (unsigned long) stats->tests_passed, FG_BOLD, @@ -108,43 +152,49 @@ void normal_log_assert(struct criterion_assert_stats *stats) { if (!stats->passed) { char *dup = strdup(*stats->message ? stats->message : stats->condition); + +#ifdef VANILLA_WIN32 + char *line = strtok(dup, "\n"); +#else char *saveptr = NULL; char *line = strtok_r(dup, "\n", &saveptr); +#endif criterion_pimportant(CRITERION_PREFIX_DASHES, - _("%1$s%2$s%3$s:%4$s%5$d%6$s: Assertion failed: %7$s\n"), + _(msg_assert_fail), FG_BOLD, stats->file, RESET, FG_RED, stats->line, RESET, line); +#ifdef VANILLA_WIN32 + while ((line = strtok(NULL, "\n"))) +#else while ((line = strtok_r(NULL, "\n", &saveptr))) - criterion_pimportant(CRITERION_PREFIX_DASHES, _(" %s\n"), line); +#endif + criterion_pimportant(CRITERION_PREFIX_DASHES, _(msg_desc), line); free(dup); } } void normal_log_test_crash(struct criterion_test_stats *stats) { criterion_pimportant(CRITERION_PREFIX_DASHES, - _("%1$s%2$s%3$s:%4$s%5$u%6$s: " - "Unexpected signal caught below this line!\n"), + _(msg_test_crash_line), FG_BOLD, stats->file, RESET, FG_RED, stats->progress, RESET); - criterion_pimportant(CRITERION_PREFIX_FAIL, _("%1$s::%2$s: CRASH!\n"), + criterion_pimportant(CRITERION_PREFIX_FAIL, _(msg_test_crash), stats->test->category, stats->test->name); } void normal_log_other_crash(UNUSED struct criterion_test_stats *stats) { criterion_pimportant(CRITERION_PREFIX_DASHES, - _("%1$sWarning! This test crashed during its setup or teardown.%2$s\n"), - FG_BOLD, RESET); + _(msg_test_other_crash), + 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("Running %1$s%2$lu%3$s test from %4$s%5$s%6$s:\n", - "Running %1$s%2$lu%3$s tests from %4$s%5$s%6$s:\n", - set->tests->size), + _s(msg_pre_suite, msg_pre_suite_pl, set->tests->size), FG_BLUE, (unsigned long) set->tests->size, RESET, FG_GOLD, set->suite.name, RESET); } diff --git a/src/log/tap.c b/src/log/tap.c index 822b92f..e8a9d97 100644 --- a/src/log/tap.c +++ b/src/log/tap.c @@ -31,6 +31,7 @@ #include "criterion/ordered-set.h" #include "timer.h" #include "config.h" +#include "posix-compat.h" void tap_log_pre_all(struct criterion_test_set *set) { size_t enabled_count = 0; @@ -81,13 +82,22 @@ void tap_log_post_test(struct criterion_test_stats *stats) { 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), *saveptr = NULL; + char *dup = strdup(*asrt->message ? asrt->message : asrt->condition); +#ifdef VANILLA_WIN32 + char *line = strtok(dup, "\n"); +#else + char *saveptr = NULL; char *line = strtok_r(dup, "\n", &saveptr); +#endif criterion_important(" %s:%u: Assertion failed: %s\n", asrt->file, asrt->line, line); +#ifdef VANILLA_WIN32 + while ((line = strtok(NULL, "\n"))) +#else while ((line = strtok_r(NULL, "\n", &saveptr))) +#endif criterion_important(" %s\n", line); free(dup); } diff --git a/src/main.c b/src/main.c index 32859c9..0402d71 100644 --- a/src/main.c +++ b/src/main.c @@ -38,7 +38,7 @@ # define VERSION_MSG "Tests compiled with Criterion v" VERSION "\n" -#ifdef HAVE_FNMATCH +#ifdef HAVE_PCRE # define PATTERN_USAGE \ " --pattern [PATTERN]: run tests matching the " \ "given pattern\n" @@ -124,7 +124,7 @@ int main(int argc, char *argv[]) { {"list", no_argument, 0, 'l'}, {"ascii", no_argument, 0, 'k'}, {"fail-fast", no_argument, 0, 'f'}, -#ifdef HAVE_FNMATCH +#ifdef HAVE_PCRE {"pattern", required_argument, 0, 'p'}, #endif {"always-succeed", no_argument, 0, 'y'}, @@ -146,7 +146,7 @@ int main(int argc, char *argv[]) { opt->fail_fast = !strcmp("1", getenv("CRITERION_FAIL_FAST") ?: "0"); opt->use_ascii = use_ascii; opt->logging_threshold = atoi(getenv("CRITERION_VERBOSITY_LEVEL") ?: "2"); -#ifdef HAVE_FNMATCH +#ifdef HAVE_PCRE opt->pattern = getenv("CRITERION_TEST_PATTERN"); #endif @@ -162,7 +162,7 @@ int main(int argc, char *argv[]) { case 'z': criterion_options.no_early_exit = true; break; case 'k': criterion_options.use_ascii = true; break; case 'f': criterion_options.fail_fast = true; break; -#ifdef HAVE_FNMATCH +#ifdef HAVE_PCRE case 'p': criterion_options.pattern = optarg; break; #endif case 't': use_tap = true; break; diff --git a/src/posix-compat.c b/src/posix-compat.c index e68f848..72afb91 100644 --- a/src/posix-compat.c +++ b/src/posix-compat.c @@ -1,9 +1,12 @@ +#include #include "posix-compat.h" #include "process.h" #ifdef VANILLA_WIN32 # define VC_EXTRALEAN # define WIN32_LEAN_AND_MEAN +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x0502 # include # include # include @@ -25,6 +28,11 @@ # define WRITE_PROCESS_(Proc, What, Size) \ WriteProcessMemory(Proc, &What, &What, Size, NULL); +# include +# ifndef SIGALRM +# define SIGALRM 14 +# endif + #else # include # include @@ -53,19 +61,64 @@ struct pipe_handle { struct worker_context g_worker_context = {.test = NULL}; #ifdef VANILLA_WIN32 -static struct criterion_test child_test; -static struct criterion_test_extra_data child_test_data; -static struct criterion_suite child_suite; -static struct criterion_test_extra_data child_suite_data; -static struct pipe_handle child_pipe; +struct full_context { + struct criterion_test test; + struct criterion_test_extra_data test_data; + struct criterion_suite suite; + struct criterion_test_extra_data suite_data; + f_worker_func func; + struct pipe_handle pipe; + int resumed; +}; + +static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker"); +#define MAPPING_SIZE sizeof (struct full_context) + +static struct full_context local_ctx; #endif int resume_child(void) { - if (g_worker_context.test) { - run_worker(&g_worker_context); - return 1; - } +#ifdef VANILLA_WIN32 + HANDLE sharedMem = OpenFileMapping( + FILE_MAP_ALL_ACCESS, + FALSE, + g_mapping_name); + + if (sharedMem == NULL) + return 0; + + struct full_context *ctx = (struct full_context *) MapViewOfFile(sharedMem, + FILE_MAP_ALL_ACCESS, + 0, + 0, + MAPPING_SIZE); + + if (ctx == NULL) + exit(-1); + + local_ctx = *ctx; + g_worker_context = (struct worker_context) { + &local_ctx.test, + &local_ctx.suite, + local_ctx.func, + &local_ctx.pipe + }; + + local_ctx.test.data = &local_ctx.test_data; + local_ctx.suite.data = &local_ctx.suite_data; + + ctx->resumed = 1; + + UnmapViewOfFile(ctx); + CloseHandle(sharedMem); + + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + + run_worker(&g_worker_context); + return 1; +#else return 0; +#endif } s_proc_handle *fork_process() { @@ -83,37 +136,48 @@ s_proc_handle *fork_process() { return (void *) -1; // Copy context over - f_worker_func child_func = g_worker_context.func; + HANDLE sharedMem = CreateFileMapping( + INVALID_HANDLE_VALUE, + NULL, + PAGE_READWRITE, + 0, + MAPPING_SIZE, + g_mapping_name); - child_test = *g_worker_context.test; - child_test_data = *g_worker_context.test->data; - child_suite = *g_worker_context.suite; - child_pipe = *g_worker_context.pipe; + if (sharedMem == NULL) + return (void *) -1; - g_worker_context = (struct worker_context) { - &child_test, - &child_suite, - child_func, - &child_pipe - }; + struct full_context *ctx = (struct full_context *) MapViewOfFile(sharedMem, + FILE_MAP_ALL_ACCESS, + 0, + 0, + MAPPING_SIZE); - child_test.data = &child_test_data; - - if (g_worker_context.suite->data) { - child_suite_data = *g_worker_context.suite->data; - child_suite.data = &child_suite_data; - WRITE_PROCESS_(info.hProcess, child_suite_data, sizeof (child_suite_data)); + if (ctx == NULL) { + CloseHandle(sharedMem); + return (void *) -1; } - WRITE_PROCESS_(info.hProcess, child_test, sizeof (child_test)); - WRITE_PROCESS_(info.hProcess, child_test_data, sizeof (child_test_data)); - WRITE_PROCESS_(info.hProcess, child_suite, sizeof (child_suite)); - WRITE_PROCESS_(info.hProcess, child_pipe, sizeof (child_pipe)); - WRITE_PROCESS_(info.hProcess, g_worker_context, sizeof (struct worker_context)); + *ctx = (struct full_context) { + .test = *g_worker_context.test, + .test_data = *g_worker_context.test->data, + .suite = *g_worker_context.suite, + .func = g_worker_context.func, + .pipe = *g_worker_context.pipe, + .resumed = 0, + }; + + if (g_worker_context.suite->data) + ctx->suite_data = *g_worker_context.suite->data; ResumeThread(info.hThread); CloseHandle(info.hThread); + while (!ctx->resumed); // wait until the child has initialized itself + + UnmapViewOfFile(ctx); + CloseHandle(sharedMem); + return unique_ptr(s_proc_handle, { info.hProcess }); #else pid_t pid = fork(); @@ -131,7 +195,40 @@ void wait_process(s_proc_handle *handle, int *status) { DWORD exit_code; GetExitCodeProcess(handle->handle, &exit_code); CloseHandle(handle->handle); - *status = exit_code << 8; + + unsigned int sig = 0; + switch (exit_code) { + case STATUS_FLOAT_DENORMAL_OPERAND: + case STATUS_FLOAT_DIVIDE_BY_ZERO: + case STATUS_FLOAT_INEXACT_RESULT: + case STATUS_FLOAT_INVALID_OPERATION: + case STATUS_FLOAT_OVERFLOW: + case STATUS_FLOAT_STACK_CHECK: + case STATUS_FLOAT_UNDERFLOW: + case STATUS_INTEGER_DIVIDE_BY_ZERO: + case STATUS_INTEGER_OVERFLOW: sig = SIGFPE; break; + + case STATUS_ILLEGAL_INSTRUCTION: + case STATUS_PRIVILEGED_INSTRUCTION: + case STATUS_NONCONTINUABLE_EXCEPTION: sig = SIGILL; break; + + case STATUS_TIMEOUT: sig = SIGALRM; break; + + case STATUS_ACCESS_VIOLATION: + case STATUS_DATATYPE_MISALIGNMENT: + case STATUS_ARRAY_BOUNDS_EXCEEDED: + case STATUS_GUARD_PAGE_VIOLATION: + case STATUS_IN_PAGE_ERROR: + case STATUS_NO_MEMORY: + case STATUS_INVALID_DISPOSITION: + case STATUS_STACK_OVERFLOW: sig = SIGSEGV; break; + + case STATUS_CONTROL_C_EXIT: sig = SIGINT; break; + + default: break; + } + + *status = sig ? sig : exit_code << 8; #else waitpid(handle->pid, status, 0); #endif @@ -206,3 +303,37 @@ bool is_current_process(s_proc_handle *proc) { return proc->pid == getpid(); #endif } + +#ifdef VANILLA_WIN32 +void *get_win_section_start(const char *section) { + PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) GetModuleHandle(NULL); + PIMAGE_NT_HEADERS ntHeader = ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)(dosHeader) + (dosHeader->e_lfanew)); + + assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE); + assert(ntHeader->Signature == IMAGE_NT_SIGNATURE); + + PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(ntHeader); + for(int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++, pSecHeader++) { + if (!strncmp((char*) pSecHeader->Name, section, 8)) { + return (char*) dosHeader + pSecHeader->VirtualAddress; + } + } + return NULL; +} + +void *get_win_section_end(const char *section) { + PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) GetModuleHandle(NULL); + PIMAGE_NT_HEADERS ntHeader = ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)(dosHeader) + (dosHeader->e_lfanew)); + + assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE); + assert(ntHeader->Signature == IMAGE_NT_SIGNATURE); + + PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(ntHeader); + for(int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++, pSecHeader++) { + if (!strncmp((char*) pSecHeader->Name, section, 8)) { + return (char*) dosHeader + (size_t) pSecHeader->VirtualAddress + pSecHeader->SizeOfRawData; + } + } + return NULL; +} +#endif diff --git a/src/posix-compat.h b/src/posix-compat.h index 477361e..0ee7a57 100644 --- a/src/posix-compat.h +++ b/src/posix-compat.h @@ -53,4 +53,14 @@ void wait_process(s_proc_handle *handle, int *status); s_proc_handle *get_current_process(); bool is_current_process(s_proc_handle *proc); +# ifdef VANILLA_WIN32 +void *get_win_section_start(const char *section); +void *get_win_section_end(const char *section); +# define GET_SECTION_START(Name) get_win_section_start(#Name) +# define GET_SECTION_END(Name) get_win_section_end(#Name) +# else +# define GET_SECTION_START(Name) SECTION_START(Name) +# define GET_SECTION_END(Name) SECTION_END(Name) +# endif + #endif /* !POSIX_COMPAT_H_ */ diff --git a/src/report.c b/src/report.c index ea13806..8f8bf39 100644 --- a/src/report.c +++ b/src/report.c @@ -31,83 +31,37 @@ #include "criterion/ordered-set.h" #include "report.h" #include "config.h" +#include "posix-compat.h" -#ifdef HAVE_FNMATCH -#include -#endif - -#define IMPL_CALL_REPORT_HOOKS(Kind) \ - IMPL_SECTION_LIMITS(f_report_hook, crit_ ## Kind); \ - void call_report_hooks_##Kind(void *data) { \ - for (f_report_hook *hook = SECTION_START(crit_ ## Kind); \ - hook < SECTION_END(crit_ ## Kind); \ - ++hook) { \ - (*hook)(data); \ - } \ +#define IMPL_CALL_REPORT_HOOKS(Kind) \ + IMPL_SECTION_LIMITS(f_report_hook, HOOK_SECTION(Kind)); \ + void call_report_hooks_##Kind(void *data) { \ + for (f_report_hook *hook = GET_SECTION_START(HOOK_SECTION(Kind)); \ + hook < (f_report_hook*) GET_SECTION_END(HOOK_SECTION(Kind)); \ + ++hook) { \ + (*hook)(data); \ + } \ } -#define IMPL_REPORT_HOOK(Type) \ - IMPL_CALL_REPORT_HOOKS(Type); \ - ReportHook(Type) +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(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); -__attribute__((always_inline)) -static inline void nothing() {} +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)() {} -#ifdef HAVE_FNMATCH -void disable_unmatching(struct criterion_test_set *set) { - FOREACH_SET(struct criterion_suite_set *s, set->suites) { - if ((s->suite.data && s->suite.data->disabled) || !s->tests) - continue; - - FOREACH_SET(struct criterion_test *test, s->tests) { - if (fnmatch(criterion_options.pattern, test->data->identifier_, 0)) - test->data->disabled = true; - } - } -} -#endif - -IMPL_REPORT_HOOK(PRE_ALL)(struct criterion_test_set *set) { -#ifdef HAVE_FNMATCH - if (criterion_options.pattern) { - disable_unmatching(set); - } -#endif - log(pre_all, set); -} - -IMPL_REPORT_HOOK(PRE_SUITE)(struct criterion_suite_set *set) { - log(pre_suite, set); -} - -IMPL_REPORT_HOOK(PRE_INIT)(struct criterion_test *test) { - log(pre_init, test); -} - -IMPL_REPORT_HOOK(PRE_TEST)(struct criterion_test *test) { - log(pre_test, test); -} - -IMPL_REPORT_HOOK(ASSERT)(struct criterion_assert_stats *stats) { - log(assert, stats); -} - -IMPL_REPORT_HOOK(TEST_CRASH)(struct criterion_test_stats *stats) { - log(test_crash, stats); -} - -IMPL_REPORT_HOOK(POST_TEST)(struct criterion_test_stats *stats) { - log(post_test, stats); -} - -IMPL_REPORT_HOOK(POST_FINI)(struct criterion_test_stats *stats) { - log(post_fini, stats); -} - -IMPL_REPORT_HOOK(POST_SUITE)(struct criterion_suite_stats *stats) { - log(post_suite, stats); -} - -IMPL_REPORT_HOOK(POST_ALL)(struct criterion_global_stats *stats) { - log(post_all, stats); -} diff --git a/src/report.h b/src/report.h index 6a740c5..aab2a10 100644 --- a/src/report.h +++ b/src/report.h @@ -28,8 +28,8 @@ # define report(Kind, Data) call_report_hooks_##Kind(Data) -# define DECL_CALL_REPORT_HOOKS(Kind) \ - DECL_SECTION_LIMITS(f_report_hook, crit_ ## Kind); \ +# define DECL_CALL_REPORT_HOOKS(Kind) \ + DECL_SECTION_LIMITS(f_report_hook, HOOK_SECTION(Kind)); \ void call_report_hooks_##Kind(void *data) DECL_CALL_REPORT_HOOKS(PRE_ALL); diff --git a/src/runner.c b/src/runner.c index b19ea48..a35a57c 100644 --- a/src/runner.c +++ b/src/runner.c @@ -27,6 +27,7 @@ #include "criterion/criterion.h" #include "criterion/options.h" #include "criterion/ordered-set.h" +#include "criterion/logging.h" #include "stats.h" #include "runner.h" #include "report.h" @@ -35,14 +36,22 @@ #include "timer.h" #include "posix-compat.h" #include "abort.h" +#include "config.h" -IMPL_SECTION_LIMITS(struct criterion_test, criterion_tests); -IMPL_SECTION_LIMITS(struct criterion_suite, crit_suites); +#ifdef HAVE_PCRE +#include "extmatch.h" +#endif + +IMPL_SECTION_LIMITS(struct criterion_test, cr_tst); +IMPL_SECTION_LIMITS(struct criterion_suite, cr_sts); // This is here to make the test suite & test sections non-empty TestSuite(); Test(,) {}; +__attribute__ ((always_inline)) +static inline void nothing() {} + int cmp_suite(void *a, void *b) { struct criterion_suite *s1 = a, *s2 = b; return strcmp(s1->name, s2->name); @@ -67,6 +76,9 @@ struct criterion_test_set *criterion_init(void) { struct criterion_ordered_set *suites = new_ordered_set(cmp_suite, dtor_suite_set); FOREACH_SUITE_SEC(s) { + if (!s->name) + break; + struct criterion_suite_set css = { .suite = *s, }; @@ -75,6 +87,9 @@ struct criterion_test_set *criterion_init(void) { size_t nb_tests = 0; FOREACH_TEST_SEC(test) { + if (!test->category) + break; + if (!*test->category) continue; @@ -109,6 +124,7 @@ static void map_tests(struct criterion_test_set *set, continue; report(PRE_SUITE, s); + log(pre_suite, s); smart struct criterion_suite_stats *suite_stats = suite_stats_init(&s->suite); @@ -124,12 +140,10 @@ static void map_tests(struct criterion_test_set *set, } report(POST_SUITE, suite_stats); + log(post_suite, suite_stats); } } -__attribute__ ((always_inline)) -static inline void nothing() {} - static void run_test_child(struct criterion_test *test, struct criterion_suite *suite) { @@ -197,15 +211,28 @@ static void run_test(struct criterion_global_stats *stats, while ((ev = worker_read_event(proc)) != NULL) { stat_push_event(stats, suite_stats, test_stats, ev); switch (ev->kind) { - case PRE_INIT: report(PRE_INIT, test); break; - case PRE_TEST: report(PRE_TEST, test); - test_started = true; - break; - case ASSERT: report(ASSERT, ev->data); break; - case POST_TEST: report(POST_TEST, test_stats); - normal_finish = true; - break; - case POST_FINI: report(POST_FINI, test_stats); break; + case PRE_INIT: + report(PRE_INIT, test); + log(pre_init, test); + break; + case PRE_TEST: + report(PRE_TEST, test); + log(pre_test, test); + test_started = true; + break; + case ASSERT: + report(ASSERT, ev->data); + log(assert, ev->data); + break; + case POST_TEST: + report(POST_TEST, test_stats); + log(post_test, test_stats); + normal_finish = true; + break; + case POST_FINI: + report(POST_FINI, test_stats); + log(post_fini, test_stats); + break; } sfree(ev); } @@ -225,21 +252,49 @@ static void run_test(struct criterion_global_stats *stats, test_stats->signal = status.status; if (test->data->signal == 0) { push_event(TEST_CRASH); + log(test_crash, test_stats); } else { double elapsed_time = 0; push_event(POST_TEST, .data = &elapsed_time); + log(post_test, test_stats); push_event(POST_FINI); + log(post_fini, test_stats); } } } +#ifdef HAVE_PCRE +void disable_unmatching(struct criterion_test_set *set) { + FOREACH_SET(struct criterion_suite_set *s, set->suites) { + if ((s->suite.data && s->suite.data->disabled) || !s->tests) + continue; + + FOREACH_SET(struct criterion_test *test, s->tests) { + const char *errmsg; + int ret = extmatch(criterion_options.pattern, test->data->identifier_, &errmsg); + if (ret == -10) { + printf("pattern error: %s\n", errmsg); + exit(1); + } else if (ret < 0) { + test->data->disabled = true; + } + } + } +} +#endif + static int criterion_run_all_tests_impl(void) { if (resume_child()) // (windows only) resume from the fork return -1; smart struct criterion_test_set *set = criterion_init(); +#ifdef HAVE_PCRE + if (criterion_options.pattern) + disable_unmatching(set); +#endif report(PRE_ALL, set); + log(pre_all, set); smart struct criterion_global_stats *stats = stats_init(); map_tests(set, stats, run_test); @@ -248,6 +303,7 @@ static int criterion_run_all_tests_impl(void) { return -1; report(POST_ALL, stats); + log(post_all, stats); return stats->tests_failed == 0; } diff --git a/src/runner.h b/src/runner.h index 9cb4156..e78fd96 100644 --- a/src/runner.h +++ b/src/runner.h @@ -25,20 +25,21 @@ # define CRITERION_RUNNER_H_ # include "criterion/types.h" +# include "posix-compat.h" -DECL_SECTION_LIMITS(struct criterion_test, criterion_tests); -DECL_SECTION_LIMITS(struct criterion_suite, crit_suites); +DECL_SECTION_LIMITS(struct criterion_test, cr_tst); +DECL_SECTION_LIMITS(struct criterion_suite, cr_sts); struct criterion_test_set *criterion_init(void); # define FOREACH_TEST_SEC(Test) \ - for (struct criterion_test *Test = SECTION_START(criterion_tests); \ - Test < SECTION_END(criterion_tests); \ + for (struct criterion_test *Test = GET_SECTION_START(cr_tst); \ + Test < (struct criterion_test*) GET_SECTION_END(cr_tst); \ ++Test) # define FOREACH_SUITE_SEC(Suite) \ - for (struct criterion_suite *Suite = SECTION_START(crit_suites); \ - Suite < SECTION_END(crit_suites); \ + for (struct criterion_suite *Suite = GET_SECTION_START(cr_sts); \ + Suite < (struct criterion_suite*) GET_SECTION_END(cr_sts); \ ++Suite) #endif /* !CRITERION_RUNNER_H_ */