diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMakeLists.txt | 129 | ||||
-rw-r--r-- | Doxyfile.in | 9 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | Simple-Makefile | 6 | ||||
-rwxr-xr-x | build.sh | 4 | ||||
-rw-r--r-- | cmake/Coveralls.cmake | 119 | ||||
-rw-r--r-- | cmake/CoverallsClear.cmake | 24 | ||||
-rw-r--r-- | cmake/CoverallsGenerateGcov.cmake | 429 | ||||
-rw-r--r-- | cmake/LCov.cmake | 6 | ||||
-rw-r--r-- | cn-cbor.pc.in | 11 | ||||
-rw-r--r-- | include/CMakeLists.txt | 2 | ||||
-rw-r--r-- | include/cn-cbor/cn-cbor.h | 196 | ||||
-rw-r--r-- | src/CMakeLists.txt | 48 | ||||
-rw-r--r-- | src/cbor.h | 43 | ||||
-rw-r--r-- | src/cn-cbor.c | 35 | ||||
-rw-r--r-- | src/cn-error.c | 1 | ||||
-rw-r--r-- | src/cn-get.c (renamed from src/cn-manip.c) | 2 | ||||
-rw-r--r-- | test/CMakeLists.txt | 33 | ||||
-rw-r--r-- | test/cbor_test.c | 219 | ||||
-rw-r--r-- | test/ctest.h | 459 | ||||
-rw-r--r-- | test/test.c | 44 |
22 files changed, 1775 insertions, 51 deletions
@@ -1,3 +1,4 @@ cntest new.out *.o +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..46022af --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,129 @@ +# +# +# top level build file for cn-cbor + +## prepare CMAKE +cmake_minimum_required ( VERSION 3.0.0 ) + +set ( VERSION_MAJOR 0 CACHE STRING "Project major version number") +set ( VERSION_MINOR "1" CACHE STRING "Project minor version number" ) +set ( VERSION_PATCH "0" CACHE STRING "Project patch version number" ) +set ( CN_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" ) +mark_as_advanced(VERSION_MAJOR VERSION_MINOR VERSION_PATCH CN_VERSION) + +project ( "cn-cbor" VERSION "${CN_VERSION}") + +find_package(Doxygen) + +## setup options +option ( use_context "Use context pointer for CBOR functions" OFF ) +option ( verbose "Produce verbose makefile output" OFF ) +option ( optimize "Optimize for size" OFF ) +option ( fatal_warnings "Treat build warnings as errors" ON ) +option ( coveralls "Generate coveralls data" ON ) +option ( coveralls_send "Send data to coveralls site" OFF ) +option ( build_docs "Create docs using Doxygen" ${DOXYGEN_FOUND} ) + +set ( dist_dir ${CMAKE_BINARY_DIR}/dist ) +set ( prefix ${CMAKE_INSTALL_PREFIX} ) +set ( exec_prefix ${CMAKE_INSTALL_PREFIX}/bin ) +set ( libdir ${CMAKE_INSTALL_PREFIX}/lib ) +set ( includedir ${CMAKE_INSTALL_PREFIX}/include ) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cn-cbor.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/cn-cbor.pc @ONLY) +install (FILES ${CMAKE_CURRENT_BINARY_DIR}/cn-cbor.pc DESTINATION lib/pkgconfig ) + +set ( package_prefix "${CMAKE_PACKAGE_NAME}-${CMAKE_SYSTEM_NAME}" ) + +set ( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${dist_dir}/bin ) +set ( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${dist_dir}/lib ) +set ( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${dist_dir}/lib ) + +if (NOT CMAKE_BUILD_TYPE) + if ( optimize ) + set ( CMAKE_BUILD_TYPE MinSizeRel ) + set ( coveralls OFF ) + set ( coveralls_send OFF ) + else () + set ( CMAKE_BUILD_TYPE Debug ) + endif () +endif() + +if ( CMAKE_C_COMPILER_ID STREQUAL "GNU" OR + CMAKE_C_COMPILER_ID MATCHES "Clang" ) + message ( STATUS "adding GCC/Clang options ") + add_definitions ( -std=gnu99 -Wall -Wextra -pedantic ) + if ( fatal_warnings ) + add_definitions ( -Werror ) + endif () + if ( optimize ) + add_definitions ( -Os ) + endif () +elseif ( MSVC ) + add_definitions ( /W3 ) + if ( fatal_warnings ) + add_definitions ( /WX ) + endif () +else () + message ( FATAL_ERROR "unhandled compiler id: ${CMAKE_C_COMPILER_ID}" ) +endif () + +if ( verbose ) + set ( CMAKE_VERBOSE_MAKEFILE ON ) +endif () + +## include the parts +add_subdirectory ( include ) +add_subdirectory ( src ) +add_subdirectory ( test ) + +install (FILES LICENSE README.md DESTINATION .) + +## setup packaging +set ( CPACK_GENERATOR "TGZ" ) +set ( CPACK_PACKAGE_VERSION "${PROJECT_VERSION}" ) +set ( CPACK_SOURCE_GENERATOR "TGZ" ) +set ( CPACK_SOURCE_IGNORE_FILES "/\\\\.git/" ) +file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/.gitignore igs) +foreach (ig IN ITEMS ${igs}) + # remove comments + string ( REGEX REPLACE "^\\s*#.*" "" ig "${ig}") + # remove any other whitespace + string ( STRIP "${ig}" ig) + # anything left? + if (ig) + # dots are literal + string ( REPLACE "." "\\\\." ig "${ig}" ) + # stars are on thars + string ( REPLACE "*" ".*" ig "${ig}" ) + list ( APPEND CPACK_SOURCE_IGNORE_FILES "/${ig}/" ) + endif() +endforeach() + +set ( CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/README.md ) +set ( CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" ) + +include ( CPack ) +include ( CTest ) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) +include ( LCov ) + +if (build_docs) + if(NOT DOXYGEN_FOUND) + message(FATAL_ERROR "Doxygen is needed to build the documentation.") + endif() + + set(doxyfile_in ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) + set(doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + configure_file(${doxyfile_in} ${doxyfile} @ONLY) + + add_custom_target(doc + COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM) + + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc) +endif() diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 0000000..7339517 --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,9 @@ +PROJECT_NAME = "@CMAKE_PROJECT_NAME@" +PROJECT_NUMBER = @CN_VERSION@ +STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ \ + @PROJECT_BINARY_DIR@ +INPUT = @PROJECT_SOURCE_DIR@/README.md \ + @PROJECT_SOURCE_DIR@/include +FILE_PATTERNS = *.h +RECURSIVE = YES +USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md @@ -19,8 +19,14 @@ changes, in order to get even closer to 0.5 KiB, but I ran out of time. So here it is. If I do get around to making these changes, the API will indeed change a bit, so please be forewarned. +## Building + There is a `Simple-Makefile` for playing around, as well as a complete [`cmake`](http://www.cmake.org)-based build environment. (You can choose what fits your needs better.) +Building with `cmake`: + + ./build.sh + License: MIT diff --git a/Simple-Makefile b/Simple-Makefile index 6dbcc89..51e877d 100644 --- a/Simple-Makefile +++ b/Simple-Makefile @@ -1,6 +1,6 @@ # enable this for armv7 builds, lazily using iPhone SDK #CFLAGS = -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include -arch armv7 -Os -CFLAGS = -Os -Wall -Wextra -Wno-unknown-pragmas -Werror-implicit-function-declaration -Werror -Wno-unused-parameter -Wdeclaration-after-statement -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Iinclude/cn-cbor +CFLAGS = -Os -Wall -Wextra -Wno-unknown-pragmas -Werror-implicit-function-declaration -Werror -Wno-unused-parameter -Wdeclaration-after-statement -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Iinclude all: cntest @@ -8,8 +8,8 @@ test: cntest (cd test; env MallocStackLogging=true ../cntest) >new.out -diff new.out test/expected.out -cntest: src/cbor.h include/cn-cbor/cn-cbor.h src/cn-cbor.c src/cn-error.c src/cn-manip.c test/test.c - clang $(CFLAGS) src/cn-cbor.c src/cn-error.c src/cn-manip.c test/test.c -o cntest +cntest: src/cbor.h include/cn-cbor/cn-cbor.h src/cn-cbor.c src/cn-error.c src/cn-get.c test/test.c + clang $(CFLAGS) src/cn-cbor.c src/cn-error.c src/cn-get.c test/test.c -o cntest size: cn-cbor.o size cn-cbor.o diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..a07f37f --- /dev/null +++ b/build.sh @@ -0,0 +1,4 @@ +if [ ! -d "build" ]; then + mkdir build +fi +cd build && cmake .. && make $* diff --git a/cmake/Coveralls.cmake b/cmake/Coveralls.cmake new file mode 100644 index 0000000..d60adba --- /dev/null +++ b/cmake/Coveralls.cmake @@ -0,0 +1,119 @@ +# +# 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 <joakim.soderberg@gmail.com> +# + + +# +# 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("Coveralls: Using alternate CMake script dir: ${_CMAKE_SCRIPT_PATH}") + else() + set(_CMAKE_SCRIPT_PATH ${PROJECT_SOURCE_DIR}/cmake) + 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} + -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_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/CoverallsClear.cmake b/cmake/CoverallsClear.cmake new file mode 100644 index 0000000..eb68695 --- /dev/null +++ b/cmake/CoverallsClear.cmake @@ -0,0 +1,24 @@ +# +# 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 <joakim.soderberg@gmail.com> +# + +file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/*.gcda) + diff --git a/cmake/CoverallsGenerateGcov.cmake b/cmake/CoverallsGenerateGcov.cmake new file mode 100644 index 0000000..bb8bd5f --- /dev/null +++ b/cmake/CoverallsGenerateGcov.cmake @@ -0,0 +1,429 @@ +# +# 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 <joakim.soderberg@gmail.com> +# +# 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}) + +find_program(GCOV_EXECUTABLE gcov) + +if (NOT GCOV_EXECUTABLE) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() + +find_package(Git) + +# TODO: Add these git things to the coveralls json. +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_EMAIL) + 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) + + 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 message: ${GIT_COMMIT_MESSAGE}") + +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}) + 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}) + + # 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_TEMPLATE +"{ + \"service_name\": \"\@JSON_SERVICE_NAME\@\", + \"service_job_id\": \"\@JSON_SERVICE_JOB_ID\@\", + \"source_files\": \@JSON_GCOV_FILES\@ +}" +) + +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}") + 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 "#####") + 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}) + + # 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}0, ") + + 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/LCov.cmake b/cmake/LCov.cmake new file mode 100644 index 0000000..ebdc665 --- /dev/null +++ b/cmake/LCov.cmake @@ -0,0 +1,6 @@ +# TODO: parameterize for reuse +add_custom_target(coverage_report + COMMAND lcov --directory src/CMakeFiles/cn-cbor.dir --capture --output-file cn-cbor.info + COMMAND genhtml --output-directory lcov cn-cbor.info + COMMAND echo "Coverage report in: file://${CMAKE_BINARY_DIR}/lcov/index.html" +) diff --git a/cn-cbor.pc.in b/cn-cbor.pc.in new file mode 100644 index 0000000..0ede53a --- /dev/null +++ b/cn-cbor.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: cn-cbor +Description: A constrained node implementation of CBOR in C +URL: https://github.com/cabo/cn-cbor +Version: @CN_VERSION@ +Libs: -L${libdir} -lcn-cbor +Cflags: -I${includedir} diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..898676a --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,2 @@ +install ( DIRECTORY ../include DESTINATION . + PATTERN CMakeLists.txt EXCLUDE ) diff --git a/include/cn-cbor/cn-cbor.h b/include/cn-cbor/cn-cbor.h index 2bf9416..56df2ba 100644 --- a/include/cn-cbor/cn-cbor.h +++ b/include/cn-cbor/cn-cbor.h @@ -1,3 +1,9 @@ +/** + * \file + * \brief + * CBOR parsing + */ + #ifndef CN_CBOR_H #define CN_CBOR_H @@ -8,67 +14,235 @@ extern "C" { } /* Duh. */ #endif +/** + * All of the different kinds of CBOR values. + */ typedef enum cn_cbor_type { + /** null */ CN_CBOR_NULL, - CN_CBOR_FALSE, CN_CBOR_TRUE, - CN_CBOR_UINT, CN_CBOR_INT, - CN_CBOR_BYTES, CN_CBOR_TEXT, - CN_CBOR_BYTES_CHUNKED, CN_CBOR_TEXT_CHUNKED, /* += 2 */ - CN_CBOR_ARRAY, CN_CBOR_MAP, + /** false */ + CN_CBOR_FALSE, + /** true */ + CN_CBOR_TRUE, + /** Positive integers */ + CN_CBOR_UINT, + /** Negative integers */ + CN_CBOR_INT, + /** Byte string */ + CN_CBOR_BYTES, + /** UTF-8 string */ + CN_CBOR_TEXT, + /** Byte string, in chunks. Each chunk is a child. */ + CN_CBOR_BYTES_CHUNKED, + /** UTF-8 string, in chunks. Each chunk is a child */ + CN_CBOR_TEXT_CHUNKED, + /** Array of CBOR values. Each array element is a child, in order */ + CN_CBOR_ARRAY, + /** Map of key/value pairs. Each key and value is a child, alternating. */ + CN_CBOR_MAP, + /** Tag describing the next value. The next value is the single child. */ CN_CBOR_TAG, - CN_CBOR_SIMPLE, CN_CBOR_DOUBLE, + /** Simple value, other than the defined ones */ + CN_CBOR_SIMPLE, + /** Doubles, floats, and half-floats */ + CN_CBOR_DOUBLE, + /** An error has occurred */ CN_CBOR_INVALID } cn_cbor_type; +/** + * Flags used during parsing. Not useful for consumers of the + * `cn_cbor` structure. + */ typedef enum cn_cbor_flags { + /** The count field will be used for parsing */ CN_CBOR_FL_COUNT = 1, + /** An indefinite number of children */ CN_CBOR_FL_INDEF = 2, + /** Not used yet; the structure must free the v.str pointer when the + structure is freed */ CN_CBOR_FL_OWNER = 0x80, /* of str */ } cn_cbor_flags; +/** + * A CBOR value + */ typedef struct cn_cbor { + /** The type of value */ cn_cbor_type type; + /** Flags used at parse time */ cn_cbor_flags flags; + /** Data associated with the value; different branches of the union are + used depending on the `type` field. */ union { + /** CN_CBOR_BYTES, CN_CBOR_TEXT */ const char* str; + /** CN_CBOR_INT */ long sint; + /** CN_CBOR_UINT */ unsigned long uint; + /** CN_CBOR_DOUBLE */ double dbl; - unsigned long count; /* for use during filling */ + /** for use during parsing */ + unsigned long count; } v; /* TBD: optimize immediate */ + /** Number of children. + * @note: for maps, this is 2x the number of entries */ int length; + /** The first child value */ struct cn_cbor* first_child; + /** The last child value */ struct cn_cbor* last_child; + /** The sibling after this one, or NULL if this is the last */ struct cn_cbor* next; + /** The parent of this value, or NULL if this is the root */ struct cn_cbor* parent; } cn_cbor; +/** + * All of the different kinds of errors + */ typedef enum cn_cbor_error { + /** No error has occurred */ CN_CBOR_NO_ERROR, + /** More data was expected while parsing */ CN_CBOR_ERR_OUT_OF_DATA, + /** Some extra data was left over at the end of parsing */ CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED, + /** A map should be alternating keys and values. A break was found + when a value was expected */ CN_CBOR_ERR_ODD_SIZE_INDEF_MAP, + /** A break was found where it wasn't expected */ CN_CBOR_ERR_BREAK_OUTSIDE_INDEF, + /** Indefinite encoding works for bstrs, strings, arrays, and maps. + A different major type tried to use it. */ CN_CBOR_ERR_MT_UNDEF_FOR_INDEF, + /** Additional Information values 28-30 are reserved */ CN_CBOR_ERR_RESERVED_AI, + /** A chunked encoding was used for a string or bstr, and one of the elements + wasn't the expected (string/bstr) type */ CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING, - CN_CBOR_ERR_OUT_OF_MEMORY, + /** An invalid parameter was passed to a function */ + CN_CBOR_ERR_INVALID_PARAMETER, + /** Allocation failed */ + CN_CBOR_ERR_OUT_OF_MEMORY } cn_cbor_error; +/** + * Strings matching the `cn_cbor_error` conditions. + * + * @todo: turn into a function to make the type safety more clear? + */ extern const char *cn_cbor_error_str[]; +/** + * Errors + */ typedef struct cn_cbor_errback { + /** The position in the input where the erorr happened */ int pos; + /** The error, or CN_CBOR_NO_ERROR if none */ cn_cbor_error err; } cn_cbor_errback; -const cn_cbor* cn_cbor_decode(const char* buf, size_t len, cn_cbor_errback *errp); +#ifdef USE_CBOR_CONTEXT + +/** + * Allocate and zero out memory. `count` elements of `size` are required, + * as for `calloc(3)`. The `context` is the `cn_cbor_context` passed in + * earlier to the CBOR routine. + * + * @param[in] count The number of items to allocate + * @param[in] size The size of each item + * @param[in] context The allocation context + */ +typedef void* (*cn_calloc_func)(size_t count, size_t size, void *context); + +/** + * Free memory previously allocated with a context. If using a pool allocator, + * this function will often be a no-op, but it must be supplied in order to + * prevent the CBOR library from calling `free(3)`. + * + * @note: it may be that this is never needed; if so, it will be removed for + * clarity and speed. + * + * @param context [description] + * @return [description] + */ +typedef void (*cn_free_func)(void *ptr, void *context); + +/** + * The allocation context. + */ +typedef struct cn_cbor_context { + /** The pool `calloc` routine. Must allocate and zero. */ + cn_calloc_func calloc_func; + /** The pool `free` routine. Often a no-op, but required. */ + cn_free_func free_func; + /** Typically, the pool object, to be used when calling `calloc_func` + * and `free_func` */ + void *context; +} cn_cbor_context; + +/** When USE_CBOR_CONTEXT is defined, many functions take an extra `context` + * parameter */ +#define CBOR_CONTEXT , cn_cbor_context *context +/** When USE_CBOR_CONTEXT is defined, some functions take an extra `context` + * parameter at the beginning */ +#define CBOR_CONTEXT_COMMA cn_cbor_context *context, + +#else + +#define CBOR_CONTEXT +#define CBOR_CONTEXT_COMMA + +#endif + +/** + * Decode an array of CBOR bytes into structures. + * + * @param[in] buf The array of bytes to parse + * @param[in] len The number of bytes in the array + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error, if NULL is returned + * @return The parsed CBOR structure, or NULL on error + */ +const cn_cbor* cn_cbor_decode(const unsigned char* buf, size_t len CBOR_CONTEXT, cn_cbor_errback *errp); + +/** + * Get a value from a CBOR map that has the given string as a key. + * + * @param[in] cb The CBOR map + * @param[in] key The string to look up in the map + * @return The matching value, or NULL if the key is not found + */ const cn_cbor* cn_cbor_mapget_string(const cn_cbor* cb, const char* key); + +/** + * Get a value from a CBOR map that has the given integer as a key. + * + * @param[in] cb The CBOR map + * @param[in] key The int to look up in the map + * @return The matching value, or NULL if the key is not found + */ const cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key); + +/** + * Get the item with the given index from a CBOR array. + * + * @param[in] cb The CBOR map + * @param[in] idx The array index + * @return The matching value, or NULL if the index is invalid + */ const cn_cbor* cn_cbor_index(const cn_cbor* cb, unsigned int idx); -const cn_cbor* cn_cbor_alloc(cn_cbor_type t); -void cn_cbor_free(const cn_cbor* js); +/** + * Free the given CBOR structure. + * + * @param[in] cb The CBOR value to free + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + */ +void cn_cbor_free(const cn_cbor* cb CBOR_CONTEXT); #ifdef __cplusplus } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..3667b40 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,48 @@ +# +# +# compiling/installing sources for cn-cbor + +set ( cbor_srcs + cn-cbor.c + cn-error.c + cn-get.c +) + +if (use_context) + add_definitions(-DUSE_CBOR_CONTEXT) +endif() +add_library ( cn-cbor SHARED ${cbor_srcs} ) +target_include_directories ( cn-cbor PUBLIC ../include ) +target_include_directories ( cn-cbor PRIVATE ../src ) + +install ( TARGETS cn-cbor + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) +if (coveralls) + include(Coveralls) + coveralls_turn_on_coverage() + + set(COVERAGE_SRCS "") + foreach (S ${cbor_srcs}) + get_filename_component(S_ABS ${S} ABSOLUTE) + list (APPEND COVERAGE_SRCS ${S_ABS}) + endforeach() + + # Create the coveralls target. + coveralls_setup( + "${COVERAGE_SRCS}" + ${coveralls_send} # If we should upload. + ) + + #add_dependencies(coveralls, all) +endif() + +add_custom_target(size + COMMAND echo "${CMAKE_BINARY_DIR}/src/CMakeFiles/cn-cbor.dir/cn-cbor.c.o" + COMMAND size "${CMAKE_BINARY_DIR}/src/CMakeFiles/cn-cbor.dir/cn-cbor.c.o" + COMMAND size -m "${CMAKE_BINARY_DIR}/src/CMakeFiles/cn-cbor.dir/cn-cbor.c.o" + DEPENDS cn-cbor +COMMENT "Output the size of the parse routine") @@ -66,4 +66,47 @@ #define IB_FLOAT4 (IB_PRIM + AI_4) #define IB_FLOAT8 (IB_PRIM + AI_8) +// These definitions are here because they aren't required for the public +// interface, and they were quite confusing in cn-cbor.h + +#ifdef USE_CBOR_CONTEXT +/** + * Allocate enough space for 1 `cn_cbor` structure. + * + * @param[in] ctx The allocation context, or NULL for calloc. + * @return A pointer to a `cn_cbor` or NULL on failure + */ +#define CN_CALLOC(ctx) ((ctx) && (ctx)->calloc_func) ? \ + (ctx)->calloc_func(1, sizeof(cn_cbor), (ctx)->context) : \ + calloc(1, sizeof(cn_cbor)); + +/** + * Free a + * @param free_func [description] + * @return [description] + */ +#define CN_FREE(ptr, ctx) ((ctx) && (ctx)->free_func) ? \ + (ctx)->free_func((ptr), (ctx)->context) : \ + free((ptr)); + +#define CBOR_CONTEXT_PARAM , context +#define CN_CALLOC_CONTEXT() CN_CALLOC(context) +#define CN_CBOR_FREE_CONTEXT(p) CN_FREE(p, context) + +#else + +#define CBOR_CONTEXT_PARAM +#define CN_CALLOC_CONTEXT() CN_CALLOC +#define CN_CBOR_FREE_CONTEXT(p) CN_FREE(p) + +#ifndef CN_CALLOC +#define CN_CALLOC calloc(1, sizeof(cn_cbor)) +#endif + +#ifndef CN_FREE +#define CN_FREE free #endif + +#endif // USE_CBOR_CONTEXT + +#endif // CBOR_PROTOCOL_H__ diff --git a/src/cn-cbor.c b/src/cn-cbor.c index 6336ee4..a4e30b0 100644 --- a/src/cn-cbor.c +++ b/src/cn-cbor.c @@ -14,18 +14,12 @@ extern "C" { #include <assert.h> #include <math.h> -#include "cn-cbor.h" +#include "cn-cbor/cn-cbor.h" #include "cbor.h" -// can be redefined, e.g. for pool allocation -#ifndef CN_CBOR_CALLOC -#define CN_CBOR_CALLOC() calloc(1, sizeof(cn_cbor)) -#define CN_CBOR_FREE(cb) free((void*)(cb)) -#endif - #define CN_CBOR_FAIL(code) do { pb->err = code; goto fail; } while(0) -void cn_cbor_free(const cn_cbor* cb) { +void cn_cbor_free(const cn_cbor* cb CBOR_CONTEXT) { cn_cbor* p = (cn_cbor*) cb; while (p) { cn_cbor* p1; @@ -36,7 +30,7 @@ void cn_cbor_free(const cn_cbor* cb) { if ((p1 = p->parent)) p1->first_child = 0; } - CN_CBOR_FREE(p); + CN_CBOR_FREE_CONTEXT(p); p = p1; } } @@ -81,7 +75,7 @@ struct parse_buf { stmt; \ pos += n; -static cn_cbor *decode_item (struct parse_buf *pb, cn_cbor* top_parent) { +static cn_cbor *decode_item (struct parse_buf *pb CBOR_CONTEXT, cn_cbor* top_parent) { unsigned char *pos = pb->buf; unsigned char *ebuf = pb->ebuf; cn_cbor* parent = top_parent; @@ -89,7 +83,7 @@ static cn_cbor *decode_item (struct parse_buf *pb, cn_cbor* top_parent) { unsigned int mt; int ai; uint64_t val; - cn_cbor* cb; + cn_cbor* cb = NULL; union { float f; uint32_t u; @@ -119,7 +113,7 @@ again: ai = ib & 0x1f; val = ai; - cb = CN_CBOR_CALLOC(); + cb = CN_CALLOC_CONTEXT(); if (!cb) CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_MEMORY); @@ -159,7 +153,7 @@ again: case MT_BYTES: case MT_TEXT: cb->v.str = (char *) pos; cb->length = val; - TAKE(pos, ebuf, val, ); + TAKE(pos, ebuf, val, ;); break; case MT_MAP: val <<= 1; @@ -222,19 +216,24 @@ fail: return 0; } -const cn_cbor* cn_cbor_decode(const char* buf, size_t len, cn_cbor_errback *errp) { +const cn_cbor* cn_cbor_decode(const unsigned char* buf, size_t len CBOR_CONTEXT, cn_cbor_errback *errp) { cn_cbor catcher = {CN_CBOR_INVALID, 0, {0}, 0, NULL, NULL, NULL, NULL}; - struct parse_buf pb = {(unsigned char *)buf, (unsigned char *)buf+len, CN_CBOR_NO_ERROR}; - cn_cbor* ret = decode_item(&pb, &catcher); + struct parse_buf pb; + cn_cbor* ret; + + pb.buf = (unsigned char *)buf; + pb.ebuf = (unsigned char *)buf+len; + pb.err = CN_CBOR_NO_ERROR; + ret = decode_item(&pb CBOR_CONTEXT_PARAM, &catcher); if (ret != NULL) { /* mark as top node */ ret->parent = NULL; } else { if (catcher.first_child) { catcher.first_child->parent = 0; - cn_cbor_free(catcher.first_child); + cn_cbor_free(catcher.first_child CBOR_CONTEXT_PARAM); } - //fail: +//fail: if (errp) { errp->err = pb.err; errp->pos = pb.buf - (unsigned char *)buf; diff --git a/src/cn-error.c b/src/cn-error.c index b1dd58c..d4407d0 100644 --- a/src/cn-error.c +++ b/src/cn-error.c @@ -7,5 +7,6 @@ const char *cn_cbor_error_str[] = { "CN_CBOR_ERR_MT_UNDEF_FOR_INDEF", "CN_CBOR_ERR_RESERVED_AI", "CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING", + "CN_CBOR_ERR_INVALID_PARAMETER", "CN_CBOR_ERR_OUT_OF_MEMORY" }; diff --git a/src/cn-manip.c b/src/cn-get.c index 130ab97..8f838d5 100644 --- a/src/cn-manip.c +++ b/src/cn-get.c @@ -2,7 +2,7 @@ #include <string.h> #include <assert.h> -#include "cn-cbor.h" +#include "cn-cbor/cn-cbor.h" const cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key) { cn_cbor* cp; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..65e95ad --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# +# Compiling/running tests + +if (use_context) + add_definitions(-DUSE_CBOR_CONTEXT) +endif() + +set ( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${dist_dir}/test ) + +function (create_test name) + add_executable ( ${name}_test ${name}_test.c ) + target_link_libraries ( ${name}_test PRIVATE cn-cbor ) + target_include_directories ( ${name}_test PRIVATE ../include ) + add_test ( NAME ${name} COMMAND ${name}_test ) +endfunction() + +create_test ( cbor ) +include ( CTest ) + +add_executable (cn-test test.c ) +target_include_directories ( cn-test PRIVATE ../include ) +target_link_libraries ( cn-test PRIVATE cn-cbor ) + +configure_file(cases.cbor cases.cbor COPYONLY) +configure_file(expected.out expected.out COPYONLY) + +add_custom_target(difftest + COMMAND env MallocStackLogging=true ./cn-test >new.out + COMMAND diff new.out expected.out + DEPENDS cn-test + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + COMMENT "generate differences between actual and expected output") diff --git a/test/cbor_test.c b/test/cbor_test.c new file mode 100644 index 0000000..b5c7d8b --- /dev/null +++ b/test/cbor_test.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2015 SPUDlib authors. See LICENSE file. + */ +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#include "cn-cbor/cn-cbor.h" + +#define CTEST_MAIN +#include "ctest.h" + +int main(int argc, const char *argv[]) +{ + return ctest_main(argc, argv); +} + +#ifdef USE_CBOR_CONTEXT +#define CONTEXT_NULL , NULL +#else +#define CONTEXT_NULL +#endif + +typedef struct _buffer { + size_t sz; + unsigned char *ptr; +} buffer; + +static bool parse_hex(char *inp, buffer *b) +{ + int len = strlen(inp); + size_t i; + if (len%2 != 0) { + b->sz = -1; + b->ptr = NULL; + return false; + } + b->sz = len / 2; + b->ptr = malloc(b->sz); + for (i=0; i<b->sz; i++) { + sscanf(inp+(2*i), "%02hhx", &b->ptr[i]); + } + return true; +} + +CTEST(cbor, error) +{ + ASSERT_STR(cn_cbor_error_str[CN_CBOR_NO_ERROR], "CN_CBOR_NO_ERROR"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_OUT_OF_DATA], "CN_CBOR_ERR_OUT_OF_DATA"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED], "CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_ODD_SIZE_INDEF_MAP], "CN_CBOR_ERR_ODD_SIZE_INDEF_MAP"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_BREAK_OUTSIDE_INDEF], "CN_CBOR_ERR_BREAK_OUTSIDE_INDEF"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_MT_UNDEF_FOR_INDEF], "CN_CBOR_ERR_MT_UNDEF_FOR_INDEF"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_RESERVED_AI], "CN_CBOR_ERR_RESERVED_AI"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING], "CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_INVALID_PARAMETER], "CN_CBOR_ERR_INVALID_PARAMETER"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_OUT_OF_MEMORY], "CN_CBOR_ERR_OUT_OF_MEMORY"); +} + +CTEST(cbor, parse) +{ + cn_cbor_errback err; + char *tests[] = { + "00", // 0 + "01", // 1 + "17", // 23 + "1818", // 24 + "190100", // 256 + "1a00010000", // 65536 + "1b0000000100000000", // 4294967296 + "20", // -1 + "37", // -24 + "3818", // -25 + "390100", // -257 + "3a00010000", // -65537 + "3b0000000100000000", // -4294967297 + "4161", // h"a" + "6161", // "a" + "8100", // [0] + "818100", // [[0]] + "a1616100", // {"a":0} + "d8184100", // tag + "f4", // false + "f5", // true + "f6", // null + "f7", // undefined + "f8ff", // simple(255) + "fb3ff199999999999a", // 1.1 + "fb7ff8000000000000", // NaN + "5f42010243030405ff", // (_ h'0102', h'030405') + "7f61616161ff", // (_ "a", "a") + "9fff", // [_ ] + "bf61610161629f0203ffff", // {_ "a": 1, "b": [_ 2, 3]} + }; + const cn_cbor *cb; + buffer b; + size_t i; +// unsigned char encoded[1024]; +// ssize_t enc_sz; + + for (i=0; i<sizeof(tests)/sizeof(char*); i++) { + ASSERT_TRUE(parse_hex(tests[i], &b)); + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + ASSERT_NOT_NULL(cb); + + // enc_sz = cbor_encoder_write(encoded, 0, sizeof(encoded), cb); + // ASSERT_EQUAL(enc_sz, b.sz); + // ASSERT_EQUAL(memcmp(b.ptr, encoded, enc_sz), 0); + // free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); + } +} + +typedef struct _cbor_failure +{ + char *hex; + cn_cbor_error err; +} cbor_failure; + +CTEST(cbor, fail) +{ + cn_cbor_errback err; + cbor_failure tests[] = { + {"81", CN_CBOR_ERR_OUT_OF_DATA}, + {"0000", CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED}, + {"bf00ff", CN_CBOR_ERR_ODD_SIZE_INDEF_MAP}, + {"ff", CN_CBOR_ERR_BREAK_OUTSIDE_INDEF}, + {"1f", CN_CBOR_ERR_MT_UNDEF_FOR_INDEF}, + {"1c", CN_CBOR_ERR_RESERVED_AI}, + {"7f4100", CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING}, + }; + const cn_cbor *cb; + buffer b; + size_t i; + + for (i=0; i<sizeof(tests)/sizeof(cbor_failure); i++) { + ASSERT_TRUE(parse_hex(tests[i].hex, &b)); + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + ASSERT_NULL(cb); + ASSERT_EQUAL(err.err, tests[i].err); + + free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); + } +} + +// Decoder loses float size information +CTEST(cbor, float) +{ + cn_cbor_errback err; + char *tests[] = { + "f9c400", // -4.0 + "fa47c35000", // 100000.0 + "f97e00", // Half NaN, half beast + "f9fc00", // -Inf + "f97c00", // Inf + }; + const cn_cbor *cb; + buffer b; + size_t i; + + for (i=0; i<sizeof(tests)/sizeof(char*); i++) { + ASSERT_TRUE(parse_hex(tests[i], &b)); + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + ASSERT_NOT_NULL(cb); + + free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); + } +} + +CTEST(cbor, getset) +{ + buffer b; + const cn_cbor *cb; + const cn_cbor *val; + cn_cbor_errback err; + + ASSERT_TRUE(parse_hex("a3436363630262626201616100", &b)); + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + ASSERT_NOT_NULL(cb); + val = cn_cbor_mapget_string(cb, "a"); + ASSERT_NOT_NULL(val); + val = cn_cbor_mapget_string(cb, "bb"); + ASSERT_NOT_NULL(val); + val = cn_cbor_mapget_string(cb, "ccc"); + ASSERT_NOT_NULL(val); + val = cn_cbor_mapget_string(cb, "b"); + ASSERT_NULL(val); + free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); + + ASSERT_TRUE(parse_hex("a2006161206162", &b)); + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + ASSERT_NOT_NULL(cb); + val = cn_cbor_mapget_int(cb, 0); + ASSERT_NOT_NULL(val); + val = cn_cbor_mapget_int(cb, -1); + ASSERT_NOT_NULL(val); + val = cn_cbor_mapget_int(cb, 1); + ASSERT_NULL(val); + free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); + + ASSERT_TRUE(parse_hex("8100", &b)); + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + ASSERT_NOT_NULL(cb); + val = cn_cbor_index(cb, 0); + ASSERT_NOT_NULL(val); + val = cn_cbor_index(cb, 1); + ASSERT_NULL(val); + val = cn_cbor_index(cb, -1); + ASSERT_NULL(val); + free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); +} diff --git a/test/ctest.h b/test/ctest.h new file mode 100644 index 0000000..75fda66 --- /dev/null +++ b/test/ctest.h @@ -0,0 +1,459 @@ +/* Copyright 2011,2012 Bas van den Berg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CTEST_H +#define CTEST_H + +#ifndef UNUSED_PARAM + /** + * \def UNUSED_PARAM(p); + * + * A macro for quelling compiler warnings about unused variables. + */ +# define UNUSED_PARAM(p) ((void)&(p)) +#endif /* UNUSED_PARM */ + +typedef void (*SetupFunc)(void*); +typedef void (*TearDownFunc)(void*); + +struct ctest { + const char* ssname; // suite name + const char* ttname; // test name + void (*run)(); + int skip; + + void* data; + SetupFunc setup; + TearDownFunc teardown; + + unsigned int magic; +}; + +#define __FNAME(sname, tname) __ctest_##sname##_##tname##_run +#define __TNAME(sname, tname) __ctest_##sname##_##tname + +#define __CTEST_MAGIC (0xdeadbeef) +#ifdef __APPLE__ +#define __Test_Section __attribute__ ((unused,section ("__DATA, .ctest"))) +#else +#define __Test_Section __attribute__ ((unused,section (".ctest"))) +#endif + +#define __CTEST_STRUCT(sname, tname, _skip, __data, __setup, __teardown) \ + struct ctest __TNAME(sname, tname) __Test_Section = { \ + .ssname=#sname, \ + .ttname=#tname, \ + .run = __FNAME(sname, tname), \ + .skip = _skip, \ + .data = __data, \ + .setup = (SetupFunc)__setup, \ + .teardown = (TearDownFunc)__teardown, \ + .magic = __CTEST_MAGIC }; + +#define CTEST_DATA(sname) struct sname##_data + +#define CTEST_SETUP(sname) \ + void __attribute__ ((weak)) sname##_setup(struct sname##_data* data) + +#define CTEST_TEARDOWN(sname) \ + void __attribute__ ((weak)) sname##_teardown(struct sname##_data* data) + +#define __CTEST_INTERNAL(sname, tname, _skip) \ + void __FNAME(sname, tname)(); \ + __CTEST_STRUCT(sname, tname, _skip, NULL, NULL, NULL) \ + void __FNAME(sname, tname)() + +#ifdef __APPLE__ +#define SETUP_FNAME(sname) NULL +#define TEARDOWN_FNAME(sname) NULL +#else +#define SETUP_FNAME(sname) sname##_setup +#define TEARDOWN_FNAME(sname) sname##_teardown +#endif + +#define __CTEST2_INTERNAL(sname, tname, _skip) \ + static struct sname##_data __ctest_##sname##_data; \ + CTEST_SETUP(sname); \ + CTEST_TEARDOWN(sname); \ + void __FNAME(sname, tname)(struct sname##_data* data); \ + __CTEST_STRUCT(sname, tname, _skip, &__ctest_##sname##_data, SETUP_FNAME(sname), TEARDOWN_FNAME(sname)) \ + void __FNAME(sname, tname)(struct sname##_data* data) + + +void CTEST_LOG(char *fmt, ...); +void CTEST_ERR(char *fmt, ...); // doesn't return + +#define CTEST(sname, tname) __CTEST_INTERNAL(sname, tname, 0) +#define CTEST_SKIP(sname, tname) __CTEST_INTERNAL(sname, tname, 1) + +#define CTEST2(sname, tname) __CTEST2_INTERNAL(sname, tname, 0) +#define CTEST2_SKIP(sname, tname) __CTEST2_INTERNAL(sname, tname, 1) + + +void assert_str(const char* exp, const char* real, const char* caller, int line); +#define ASSERT_STR(exp, real) assert_str(exp, real, __FILE__, __LINE__) + +void assert_data(const unsigned char* exp, int expsize, + const unsigned char* real, int realsize, + const char* caller, int line); +#define ASSERT_DATA(exp, expsize, real, realsize) \ + assert_data(exp, expsize, real, realsize, __FILE__, __LINE__) + +void assert_equal(long exp, long real, const char* caller, int line); +#define ASSERT_EQUAL(exp, real) assert_equal(exp, real, __FILE__, __LINE__) + +void assert_not_equal(long exp, long real, const char* caller, int line); +#define ASSERT_NOT_EQUAL(exp, real) assert_not_equal(exp, real, __FILE__, __LINE__) + +void assert_null(void* real, const char* caller, int line); +#define ASSERT_NULL(real) assert_null((void*)real, __FILE__, __LINE__) + +void assert_not_null(const void* real, const char* caller, int line); +#define ASSERT_NOT_NULL(real) assert_not_null(real, __FILE__, __LINE__) + +void assert_true(int real, const char* caller, int line); +#define ASSERT_TRUE(real) assert_true(real, __FILE__, __LINE__) + +void assert_false(int real, const char* caller, int line); +#define ASSERT_FALSE(real) assert_false(real, __FILE__, __LINE__) + +void assert_fail(const char* caller, int line); +#define ASSERT_FAIL() assert_fail(__FILE__, __LINE__) + +#ifdef CTEST_MAIN + +#include <setjmp.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> +#include <inttypes.h> +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> + +#ifdef __APPLE__ +#include <dlfcn.h> +#endif + +//#define COLOR_OK + +static size_t ctest_errorsize; +static char* ctest_errormsg; +#define MSG_SIZE 4096 +static char ctest_errorbuffer[MSG_SIZE]; +static jmp_buf ctest_err; +static int color_output = 1; +static const char* suite_name; + +typedef int (*filter_func)(struct ctest*); + +#define ANSI_BLACK "\033[0;30m" +#define ANSI_RED "\033[0;31m" +#define ANSI_GREEN "\033[0;32m" +#define ANSI_YELLOW "\033[0;33m" +#define ANSI_BLUE "\033[0;34m" +#define ANSI_MAGENTA "\033[0;35m" +#define ANSI_CYAN "\033[0;36m" +#define ANSI_GREY "\033[0;37m" +#define ANSI_DARKGREY "\033[01;30m" +#define ANSI_BRED "\033[01;31m" +#define ANSI_BGREEN "\033[01;32m" +#define ANSI_BYELLOW "\033[01;33m" +#define ANSI_BBLUE "\033[01;34m" +#define ANSI_BMAGENTA "\033[01;35m" +#define ANSI_BCYAN "\033[01;36m" +#define ANSI_WHITE "\033[01;37m" +#define ANSI_NORMAL "\033[0m" + +static CTEST(suite, test) { } + +static void msg_start(const char* color, const char* title) { + int size; + if (color_output) { + size = snprintf(ctest_errormsg, ctest_errorsize, "%s", color); + ctest_errorsize -= size; + ctest_errormsg += size; + } + size = snprintf(ctest_errormsg, ctest_errorsize, " %s: ", title); + ctest_errorsize -= size; + ctest_errormsg += size; +} + +static void msg_end() { + int size; + if (color_output) { + size = snprintf(ctest_errormsg, ctest_errorsize, ANSI_NORMAL); + ctest_errorsize -= size; + ctest_errormsg += size; + } + size = snprintf(ctest_errormsg, ctest_errorsize, "\n"); + ctest_errorsize -= size; + ctest_errormsg += size; +} + +void CTEST_LOG(char *fmt, ...) +{ + va_list argp; + msg_start(ANSI_BLUE, "LOG"); + + va_start(argp, fmt); + int size = vsnprintf(ctest_errormsg, ctest_errorsize, fmt, argp); + ctest_errorsize -= size; + ctest_errormsg += size; + va_end(argp); + + msg_end(); +} + +void CTEST_ERR(char *fmt, ...) +{ + va_list argp; + msg_start(ANSI_YELLOW, "ERR"); + + va_start(argp, fmt); + int size = vsnprintf(ctest_errormsg, ctest_errorsize, fmt, argp); + ctest_errorsize -= size; + ctest_errormsg += size; + va_end(argp); + + msg_end(); + longjmp(ctest_err, 1); +} + +void assert_str(const char* exp, const char* real, const char* caller, int line) { + if ((exp == NULL && real != NULL) || + (exp != NULL && real == NULL) || + (exp && real && strcmp(exp, real) != 0)) { + CTEST_ERR("%s:%d expected '%s', got '%s'", caller, line, exp, real); + } +} + +void assert_data(const unsigned char* exp, int expsize, + const unsigned char* real, int realsize, + const char* caller, int line) { + int i; + if (expsize != realsize) { + CTEST_ERR("%s:%d expected %d bytes, got %d", caller, line, expsize, realsize); + } + for (i=0; i<expsize; i++) { + if (exp[i] != real[i]) { + CTEST_ERR("%s:%d expected 0x%02x at offset %d got 0x%02x", + caller, line, exp[i], i, real[i]); + } + } +} + +void assert_equal(long exp, long real, const char* caller, int line) { + if (exp != real) { + CTEST_ERR("%s:%d expected %ld, got %ld", caller, line, exp, real); + } +} + +void assert_not_equal(long exp, long real, const char* caller, int line) { + if ((exp) == (real)) { + CTEST_ERR("%s:%d should not be %ld", caller, line, real); + } +} + +void assert_null(void* real, const char* caller, int line) { + if ((real) != NULL) { + CTEST_ERR("%s:%d should be NULL", caller, line); + } +} + +void assert_not_null(const void* real, const char* caller, int line) { + if (real == NULL) { + CTEST_ERR("%s:%d should not be NULL", caller, line); + } +} + +void assert_true(int real, const char* caller, int line) { + if ((real) == 0) { + CTEST_ERR("%s:%d should be true", caller, line); + } +} + +void assert_false(int real, const char* caller, int line) { + if ((real) != 0) { + CTEST_ERR("%s:%d should be false", caller, line); + } +} + +void assert_fail(const char* caller, int line) { + CTEST_ERR("%s:%d shouldn't come here", caller, line); +} + + +static int suite_all(struct ctest* t) { + UNUSED_PARAM(t); + return 1; +} + +static int suite_filter(struct ctest* t) { + return strncmp(suite_name, t->ssname, strlen(suite_name)) == 0; +} + +static uint64_t getCurrentTime() { + struct timeval now; + gettimeofday(&now, NULL); + uint64_t now64 = now.tv_sec; + now64 *= 1000000; + now64 += (now.tv_usec); + return now64; +} + +static void color_print(const char* color, const char* text) { + if (color_output) + printf("%s%s"ANSI_NORMAL"\n", color, text); + else + printf("%s\n", text); +} + +#ifdef __APPLE__ +static void *find_symbol(struct ctest *test, const char *fname) +{ + size_t len = strlen(test->ssname) + 1 + strlen(fname); + char *symbol_name = (char *) malloc(len + 1); + memset(symbol_name, 0, len + 1); + snprintf(symbol_name, len + 1, "%s_%s", test->ssname, fname); + + //fprintf(stderr, ">>>> dlsym: loading %s\n", symbol_name); + void *symbol = dlsym(RTLD_DEFAULT, symbol_name); + if (!symbol) { + //fprintf(stderr, ">>>> ERROR: %s\n", dlerror()); + } + // returns NULL on error + + free(symbol_name); + return symbol; +} +#endif + +#ifdef CTEST_SEGFAULT +#include <signal.h> +static void sighandler(int signum) +{ + char msg[128]; + sprintf(msg, "[SIGNAL %d: %s]", signum, sys_siglist[signum]); + color_print(ANSI_BRED, msg); + fflush(stdout); + + /* "Unregister" the signal handler and send the signal back to the process + * so it can terminate as expected */ + signal(signum, SIG_DFL); + kill(getpid(), signum); +} +#endif + +int ctest_main(int argc, const char *argv[]) +{ + static int total = 0; + static int num_ok = 0; + static int num_fail = 0; + static int num_skip = 0; + static int index = 1; + static filter_func filter = suite_all; + +#ifdef CTEST_SEGFAULT + signal(SIGSEGV, sighandler); +#endif + + if (argc == 2) { + suite_name = argv[1]; + filter = suite_filter; + } + + color_output = isatty(1); + uint64_t t1 = getCurrentTime(); + + struct ctest* ctest_begin = &__TNAME(suite, test); + struct ctest* ctest_end = &__TNAME(suite, test); + // find begin and end of section by comparing magics + while (1) { + struct ctest* t = ctest_begin-1; + if (t->magic != __CTEST_MAGIC) break; + ctest_begin--; + } + while (1) { + struct ctest* t = ctest_end+1; + if (t->magic != __CTEST_MAGIC) break; + ctest_end++; + } + ctest_end++; // end after last one + + static struct ctest* test; + for (test = ctest_begin; test != ctest_end; test++) { + if (test == &__ctest_suite_test) continue; + if (filter(test)) total++; + } + + for (test = ctest_begin; test != ctest_end; test++) { + if (test == &__ctest_suite_test) continue; + if (filter(test)) { + ctest_errorbuffer[0] = 0; + ctest_errorsize = MSG_SIZE-1; + ctest_errormsg = ctest_errorbuffer; + printf("TEST %d/%d %s:%s ", index, total, test->ssname, test->ttname); + fflush(stdout); + if (test->skip) { + color_print(ANSI_BYELLOW, "[SKIPPED]"); + num_skip++; + } else { + int result = setjmp(ctest_err); + if (result == 0) { +#ifdef __APPLE__ + if (!test->setup) { + test->setup = (SetupFunc)find_symbol(test, "setup"); + } + if (!test->teardown) { + test->teardown = (SetupFunc)find_symbol(test, "teardown"); + } +#endif + + if (test->setup) test->setup(test->data); + if (test->data) + test->run(test->data); + else + test->run(); + if (test->teardown) test->teardown(test->data); + // if we got here it's ok +#ifdef COLOR_OK + color_print(ANSI_BGREEN, "[OK]"); +#else + printf("[OK]\n"); +#endif + num_ok++; + } else { + color_print(ANSI_BRED, "[FAIL]"); + num_fail++; + } + if (ctest_errorsize != MSG_SIZE-1) printf("%s", ctest_errorbuffer); + } + index++; + } + } + uint64_t t2 = getCurrentTime(); + + const char* color = (num_fail) ? ANSI_BRED : ANSI_GREEN; + char results[80]; + sprintf(results, "RESULTS: %d tests (%d ok, %d failed, %d skipped) ran in %"PRIu64" ms", total, num_ok, num_fail, num_skip, (t2 - t1)/1000); + color_print(color, results); + return num_fail; +} + +#endif + +#endif diff --git a/test/test.c b/test/test.c index 5b949ee..8fc931f 100644 --- a/test/test.c +++ b/test/test.c @@ -1,14 +1,21 @@ #include <unistd.h> -#include "cn-cbor.h" #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> +#include "cn-cbor/cn-cbor.h" + +#ifdef USE_CBOR_CONTEXT +#define CBOR_CONTEXT_PARAM , NULL +#else +#define CBOR_CONTEXT_PARAM +#endif + #define ERROR(msg, p) fprintf(stderr, "ERROR: " msg " %s\n", (p)); -static char* load_file(const char* filepath, char **end) { +static unsigned char* load_file(const char* filepath, unsigned char **end) { struct stat st; if (stat(filepath, &st)==-1) { ERROR("can't find file", filepath); @@ -19,7 +26,7 @@ static char* load_file(const char* filepath, char **end) { ERROR("can't open file", filepath); return 0; } - char* text=malloc(st.st_size+1); // this is not going to be freed + unsigned char* text=malloc(st.st_size+1); // this is not going to be freed if (st.st_size!=read(fd, text, st.st_size)) { ERROR("can't read file", filepath); close(fd); @@ -92,9 +99,9 @@ const char *err_name[] = { "CN_CBOR_ERR_OUT_OF_MEMORY", }; -static void cn_cbor_decode_test(const char *buf, int len) { +static void cn_cbor_decode_test(const unsigned char *buf, int len) { struct cn_cbor_errback back; - const cn_cbor *ret = cn_cbor_decode(buf, len, &back); + const cn_cbor *ret = cn_cbor_decode(buf, len CBOR_CONTEXT_PARAM, &back); if (ret) printf("oops 1"); printf("%s at %d\n", err_name[back.err], back.pos); @@ -102,24 +109,25 @@ static void cn_cbor_decode_test(const char *buf, int len) { int main() { char buf[100000]; - char *end; - char *s = load_file("cases.cbor", &end); + unsigned char *end; + char *bufend; + unsigned char *s = load_file("cases.cbor", &end); printf("%zd\n", end-s); - const cn_cbor *cb = cn_cbor_decode(s, end-s, 0); + const cn_cbor *cb = cn_cbor_decode(s, end-s CBOR_CONTEXT_PARAM, 0); if (cb) { - dump(cb, buf, &end, 0); - *end = 0; + dump(cb, buf, &bufend, 0); + *bufend = 0; printf("%s\n", buf); - cn_cbor_free(cb); + cn_cbor_free(cb CBOR_CONTEXT_PARAM); cb = 0; /* for leaks testing */ } - cn_cbor_decode_test("\xff", 1); /* break outside indef */ - cn_cbor_decode_test("\x1f", 1); /* mt undef for indef */ - cn_cbor_decode_test("\x00\x00", 2); /* not all data consumed */ - cn_cbor_decode_test("\x81", 1); /* out of data */ - cn_cbor_decode_test("\x1c", 1); /* reserved ai */ - cn_cbor_decode_test("\xbf\x00\xff", 3); /* odd size indef map */ - cn_cbor_decode_test("\x7f\x40\xff", 3); /* wrong nesting in indef string */ + cn_cbor_decode_test((const unsigned char*)"\xff", 1); /* break outside indef */ + cn_cbor_decode_test((const unsigned char*)"\x1f", 1); /* mt undef for indef */ + cn_cbor_decode_test((const unsigned char*)"\x00\x00", 2); /* not all data consumed */ + cn_cbor_decode_test((const unsigned char*)"\x81", 1); /* out of data */ + cn_cbor_decode_test((const unsigned char*)"\x1c", 1); /* reserved ai */ + cn_cbor_decode_test((const unsigned char*)"\xbf\x00\xff", 3); /* odd size indef map */ + cn_cbor_decode_test((const unsigned char*)"\x7f\x40\xff", 3); /* wrong nesting in indef string */ system("leaks test"); } |