diff options
author | Paul Bartell <pbartell@amazon.com> | 2021-03-15 17:01:29 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-15 17:01:29 -0700 |
commit | d7e5f4088538f24b8d61a081fc7313e2d297bd2a (patch) | |
tree | a9505480e08a1154b76164c962a544f5a68c3a03 | |
parent | c8fa483b68c6c1149c2a7a8bc1e901b38860ec9b (diff) | |
download | freertos-git-d7e5f4088538f24b8d61a081fc7313e2d297bd2a.tar.gz |
Clean up CMock makefiles and add coverage filtering (#523)
* Cleanup Makefiles
* Add lcovrc configuration file
* Add CMock test build directory to .gitignore
* Add callgraph.py and filtercov.py scripts
* Cleanup list Makefile and update list_utest.c with coverage tags
* Add information about coverage filtering and running single test cases
* Remove -fprofile-exclude-files for compatibility with older versions of gcc.
Fix line endings (change to unix style)
* Lint callgraph.py and filtercov.py. Print and error when no target functions are defined.
* Indent with spaces when possible
* Replace tabs with spaces and enable .RECIPEPREFIX
* Add fake_port.h and related portmacro.h changes
* Fix list makefile when bin directory is not available
* Clean up grouped rules
* Update makesfile.. Add "two_tests" example dir
* Fix memory checker error
* Move common makefile items to subdir.mk and testdir.mk includes
* Update core_checker.py exclusions
* Remove line from portmacro.h that doesn't match core_checker.py
-rwxr-xr-x | .github/scripts/core_checker.py | 4 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/.gitignore | 1 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/Makefile | 172 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/Readme.md | 37 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/config/fake_port.h | 46 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/config/portmacro.h | 155 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/doc/Makefile | 5 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/lcovrc | 3 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/list/Makefile | 87 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/list/list_utest.c | 18 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/makefile.in | 111 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/queue/Makefile | 81 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/queue/queue_utest.c | 17 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/queue/two_tests/Makefile | 47 | ||||
l--------- | FreeRTOS/Test/CMock/queue/two_tests/queue_1_utest.c | 1 | ||||
l--------- | FreeRTOS/Test/CMock/queue/two_tests/queue_2_utest.c | 1 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/subdir.mk | 46 | ||||
-rw-r--r-- | FreeRTOS/Test/CMock/testdir.mk | 201 | ||||
-rwxr-xr-x | FreeRTOS/Test/CMock/tools/callgraph.py | 106 | ||||
-rwxr-xr-x | FreeRTOS/Test/CMock/tools/filtercov.py | 326 |
20 files changed, 1123 insertions, 342 deletions
diff --git a/.github/scripts/core_checker.py b/.github/scripts/core_checker.py index ec835048c..97e9bd590 100755 --- a/.github/scripts/core_checker.py +++ b/.github/scripts/core_checker.py @@ -175,6 +175,7 @@ FREERTOS_IGNORED_EXTENSIONS = [ '.properties', '.ps1', '.ptf', + '.py', '.r79', '.rapp', '.rc', @@ -263,7 +264,8 @@ FREERTOS_IGNORED_FILES = [ 'mbedtls_config.h', 'requirements.txt', 'run-cbmc-proofs.py', - '.editorconfig' + '.editorconfig', + 'lcovrc' ] FREERTOS_HEADER = [ diff --git a/FreeRTOS/Test/CMock/.gitignore b/FreeRTOS/Test/CMock/.gitignore new file mode 100644 index 000000000..567609b12 --- /dev/null +++ b/FreeRTOS/Test/CMock/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/FreeRTOS/Test/CMock/Makefile b/FreeRTOS/Test/CMock/Makefile index d8863dc85..a44dfe4a2 100644 --- a/FreeRTOS/Test/CMock/Makefile +++ b/FreeRTOS/Test/CMock/Makefile @@ -1,98 +1,116 @@ +# indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) +include makefile.in + # Change to match installed location export CC ?= /usr/local/bin/gcc export LD ?= /usr/local/bin/ld + # Add units here when adding a new unit test directory with the same name -UNITS := queue list timers +UNITS += list +#UNITS += queue +#UNITS += timers -include makefile.in +COVINFO := $(BUILD_DIR)/cmock_test.info +LCOV_LIST := $(foreach unit,$(UNITS),$(GENERATED_DIR)/$(unit).info ) -.PHONY: all doc clean $(UNITS) directories coverage zero_coverage -.PHONY: run run_formatted run_col_formatted run_col +.PHONY: all doc clean $(UNITS) directories coverage zero_coverage \ + run run_formatted run_col_formatted run_col libs execs lcov \ + help all: doc coverage -execs: $(UNITS) | directories - -$(UNITS) : ${LIB_DIR}/libcmock.so \ - ${LIB_DIR}/libunity.so \ - ${LIB_DIR}/libunitymemory.so \ - | directories - $(MAKE) -C $@ - -doc: | directories - $(MAKE) -C doc all - -directories : $(BUILD_DIR) $(GENERATED_DIR) $(COVERAGE_DIR) $(BIN_DIR) $(DOC_DIR) $(LIB_DIR) - -$(BUILD_DIR) : - -mkdir $(BUILD_DIR) -$(GENERATED_DIR) : - -mkdir -p $(GENERATED_DIR) -$(COVERAGE_DIR) : - -mkdir -p $(COVERAGE_DIR) -$(BIN_DIR) : - -mkdir -p $(BIN_DIR) -$(DOC_DIR) : - -mkdir -p $(DOC_DIR) -$(LIB_DIR) : - -mkdir -p $(LIB_DIR) +execs: $(UNITS) + +$(UNITS) : libs | directories + $(MAKE) -C $@ + +SHARED_LIBS := $(addprefix $(LIB_DIR)/,$(addsuffix .so,$(LIBS))) + +libs : $(SHARED_LIBS) + +doc: + $(MAKE) -C doc all clean: - rm -rf build + rm -rf build help: - @echo -e 'Usage: $$ make <unit>\n ' - @echo -e ' where <unit> is one of: $(UNITS) doc all run run_formatted run_col run_col_formatted coverage' + @echo -e 'Usage: $$ make <unit>\n ' + @echo -e ' where <unit> is one of: $(UNITS) doc all run run_formatted run_col run_col_formatted coverage' $(LIB_DIR)/libcmock.so : ${CMOCK_SRC_DIR}/cmock.c \ - ${CMOCK_SRC_DIR}/cmock.h \ - ${LIB_DIR}/libunity.so \ - Makefile | directories - ${CC} -o $@ -shared -fPIC $< ${INCLUDE_DIR} + ${CMOCK_SRC_DIR}/cmock.h \ + ${LIB_DIR}/libunity.so \ + Makefile + mkdir -p $(LIB_DIR) + ${CC} -o $@ -shared -fPIC $< ${INCLUDE_DIR} $(LIB_DIR)/libunity.so : ${UNITY_SRC_DIR}/unity.c \ - ${UNITY_SRC_DIR}/unity.h \ - Makefile | directories - ${CC} -o $@ -shared -fPIC $< - -$(LIB_DIR)/libunitymemory.so: ${UNITY_EXTRAS_DIR}/memory/src/unity_memory.c \ - ${UNITY_EXTRAS_DIR}/memory/src/unity_memory.h \ - ${LIB_DIR}/libunity.so \ - Makefile | directories - ${CC} -o $@ -shared -fPIC $< ${INCLUDE_DIR} - -run : $(UNITS) directories - -rm $(BUILD_DIR)/unit_test_report.txt - for f in $(BIN_DIR)/*; do \ - $${f} | tee -a $(BUILD_DIR)/unit_test_report.txt; \ - done - cd $(BUILD_DIR) && \ - ruby $(UNITY_BIN_DIR)/parse_output.rb -xml \ - $(BUILD_DIR)/unit_test_report.txt - -run_col : $(UNITS) zero_coverage | directories - for f in $(BIN_DIR)/*; do \ - ruby -r $(UNITY_BIN_DIR)/colour_reporter.rb -e "report('`$${f}`')"; done - -run_formatted : $(UNITS) zero_coverage | directories - for f in $(BIN_DIR)/*; do \ - $${f} > $(BUILD_DIR)/output; \ - ruby $(UNITY_BIN_DIR)/parse_output.rb $(BUILD_DIR)/output ; \ - done - -run_col_formatted : $(UNITS) zero_coverage | directories - for f in $(BIN_DIR)/*; do \ - $${f} > $(BUILD_DIR)/output; \ - ruby -r $(UNITY_BIN_DIR)/colour_reporter.rb \ - -e "report('$$(ruby $(UNITY_BIN_DIR)/parse_output.rb \ - $(BUILD_DIR)/output)')"; \ - done + ${UNITY_SRC_DIR}/unity.h \ + Makefile + mkdir -p $(LIB_DIR) + ${CC} -o $@ -shared -fPIC $< + +$(LIB_DIR)/libunitymemory.so: ${UNITY_MEM_DIR}/unity_memory.c \ + ${UNITY_MEM_DIR}/unity_memory.h \ + ${LIB_DIR}/libunity.so \ + Makefile + mkdir -p $(LIB_DIR) + ${CC} -o $@ -shared -fPIC $< ${INCLUDE_DIR} + +$(LIB_DIR)/libcexception.so: ${C_EXCEPTION_SRC_DIR}/CException.c \ + ${C_EXCEPTION_SRC_DIR}/CException.h + mkdir -p $(LIB_DIR) + ${CC} -o $@ -shared -fPIC $< ${INCLUDE_DIR} + +run : $(UNITS) + mkdir -p $(BUILD_DIR) + rm -f $(BUILD_DIR)/unit_test_report.txt + for f in $(BIN_DIR)/*; do \ + $${f} | tee -a $(BUILD_DIR)/unit_test_report.txt; \ + done + cd $(BUILD_DIR) && \ + ruby $(UNITY_BIN_DIR)/parse_output.rb -xml \ + $(BUILD_DIR)/unit_test_report.txt + +run_col : $(UNITS) zero_coverage + for f in $(BIN_DIR)/*; do \ + ruby -r $(UNITY_BIN_DIR)/colour_reporter.rb -e "report('`$${f}`')"; done + +run_formatted : $(UNITS) zero_coverage + for f in $(BIN_DIR)/*; do \ + $${f} > $(BUILD_DIR)/output; \ + ruby $(UNITY_BIN_DIR)/parse_output.rb $(BUILD_DIR)/output ; \ + done + +run_col_formatted : $(UNITS) zero_coverage + for f in $(BIN_DIR)/*; do \ + $${f} > $(BUILD_DIR)/output; \ + ruby -r $(UNITY_BIN_DIR)/colour_reporter.rb \ + -e "report('$$(ruby $(UNITY_BIN_DIR)/parse_output.rb \ + $(BUILD_DIR)/output)')"; \ + done zero_coverage : - lcov --zerocounters --directory $(BUILD_DIR) + lcov --zerocounters --directory $(BUILD_DIR) --config-file $(UT_ROOT_DIR)/lcovrc + coverage : run_col - lcov --base-directory . --directory . -c --rc lcov_branch_coverage=1 \ - --rc genhtml_branch_coverage=1 -o $(BUILD_DIR)/cmock_test.info - genhtml $(BUILD_DIR)/cmock_test.info --branch-coverage \ - --output-directory $(COVERAGE_DIR) + lcov --base-directory $(KERNEL_DIR) --directory $(BUILD_DIR) --capture \ + --config-file $(UT_ROOT_DIR)/lcovrc -o $(BUILD_DIR)/cmock_test.info + genhtml $(BUILD_DIR)/cmock_test.info --branch-coverage \ + --config-file $(UT_ROOT_DIR)/lcovrc --output-directory $(COVERAGE_DIR) + +lcov : $(COVINFO) + +# Combine lcov from each test bin into one lcov info file for the suite +$(COVINFO) : $(LCOV_LIST) + lcov $(LCOV_OPTS) -o $@ $(foreach cov,$(LCOV_LIST),--add-tracefile $(cov) ) +# Generate lcov for each suite +$(LCOV_LIST) : zero_coverage + $(foreach unit,$(UNITS),\ + make -C $(unit) lcov;) +lcovhtml : $(COVINFO) + mkdir -p $(COVERAGE_DIR) + genhtml $(COVINFO) $(LCOV_OPTS) --output-directory $(COVERAGE_DIR) --quiet diff --git a/FreeRTOS/Test/CMock/Readme.md b/FreeRTOS/Test/CMock/Readme.md index df2c7c105..059f65303 100644 --- a/FreeRTOS/Test/CMock/Readme.md +++ b/FreeRTOS/Test/CMock/Readme.md @@ -21,6 +21,14 @@ Doxygen (optional) ``` 1.8.5 ``` +Python (optional, required for coverage filtering) +``` +Python 3.8 or later +``` +Cflow (optional, required for coverage filtering) +``` +cflow (GNU cflow) 1.6 +``` ## How to run ``` $ make help @@ -41,7 +49,7 @@ $ make doc Would generate the doxygen documentation in build/doc ``` -$ make run | run_formatted | run_col | run_col_formatted +$ make run | run_formatted | run_col | run_col_formatted ``` Would build all unit tests and runs them one after the other with different options between normal and formatted and colored for easily spotting errors @@ -52,3 +60,30 @@ $ make coverage Would build all unit tests, runs them one after the other, then generates html code coverage and places them in build/coverage with initial file index.html +## Running individual tests +From each test directory, you can build, run the test, and generate gcov coverage with the default "all" target like so: +``` +$ make -C list +``` +For convenience, a "gcov" target is also provided. +``` +$ make -C list gcov +``` +Alternatively, you can generate filtered coverage with the "lcov" target: +``` +$ make -C list lcov +``` +You can also generate an html coverage report with the lcovhtml target: +``` +$ make -C list lcovhtml +``` + +## Coverage Filtering ## +Coverage filtering is meant to remove "unintentional" or "incidental" test coverage that is generated by other test cases which call a specific function but are not meant to test that function. +In order to use coverage filtering and the associated lcov and lcovhtml targets, you must install the "optional" requirements listed above. + + +Additionally, you must also document which functions you are targeting inside each _utest.c file with a tag inside each file similar to the following: +``` +@coverage vFunctionNameHere vAnotherFunctionNameHere +``` diff --git a/FreeRTOS/Test/CMock/config/fake_port.h b/FreeRTOS/Test/CMock/config/fake_port.h new file mode 100644 index 000000000..b65540549 --- /dev/null +++ b/FreeRTOS/Test/CMock/config/fake_port.h @@ -0,0 +1,46 @@ +/* + * FreeRTOS V202012.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * 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. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +#ifndef FAKE_PORT_H +#define FAKE_PORT_H + +void vFakePortYield( void ); +void vFakePortYieldFromISR( void ); +void vFakePortYieldWithinAPI( void ); + +void vFakePortDisableInterrupts( void ); +void vFakePortEnableInterrupts( void ); +void vFakePortClearInterruptMaskFromISR( UBaseType_t uxNewMaskValue ); +void vFakePortClearInterruptMask( UBaseType_t uxNewMaskValue ); +UBaseType_t ulFakePortSetInterruptMaskFromISR( void ); +UBaseType_t ulFakePortSetInterruptMask( void ); + +void vFakePortAssertIfInterruptPriorityInvalid( void ); + +void vFakePortEnterCriticalSection( void ); +void vFakePortExitCriticalSection( void ); + +#endif /* FAKE_PORT_H */ diff --git a/FreeRTOS/Test/CMock/config/portmacro.h b/FreeRTOS/Test/CMock/config/portmacro.h index 060739a69..be2c9d1d1 100644 --- a/FreeRTOS/Test/CMock/config/portmacro.h +++ b/FreeRTOS/Test/CMock/config/portmacro.h @@ -22,38 +22,37 @@ * https://www.FreeRTOS.org * https://github.com/FreeRTOS * - * 1 tab == 4 spaces! */ /* - Changes from V3.2.3 - - + Modified portENTER_SWITCHING_ISR() to allow use with GCC V4.0.1. - - Changes from V3.2.4 - - + Removed the use of the %0 parameter within the assembler macros and - replaced them with hard coded registers. This will ensure the - assembler does not select the link register as the temp register as - was occasionally happening previously. - - + The assembler statements are now included in a single asm block rather - than each line having its own asm block. - - Changes from V4.5.0 - - + Removed the portENTER_SWITCHING_ISR() and portEXIT_SWITCHING_ISR() macros - and replaced them with portYIELD_FROM_ISR() macro. Application code - should now make use of the portSAVE_CONTEXT() and portRESTORE_CONTEXT() - macros as per the V4.5.1 demo code. -*/ + * Changes from V3.2.3 + * + + Modified portENTER_SWITCHING_ISR() to allow use with GCC V4.0.1. + + + + Changes from V3.2.4 + + + + Removed the use of the %0 parameter within the assembler macros and + + replaced them with hard coded registers. This will ensure the + + assembler does not select the link register as the temp register as + + was occasionally happening previously. + + + + The assembler statements are now included in a single asm block rather + + than each line having its own asm block. + + + + Changes from V4.5.0 + + + + Removed the portENTER_SWITCHING_ISR() and portEXIT_SWITCHING_ISR() macros + + and replaced them with portYield_FROM_ISR() macro. Application code + + should now make use of the portSAVE_CONTEXT() and portRESTORE_CONTEXT() + + macros as per the V4.5.1 demo code. + */ #ifndef PORTMACRO_H -#define PORTMACRO_H + #define PORTMACRO_H -#ifdef __cplusplus -extern "C" { -#endif + #ifdef __cplusplus + extern "C" { + #endif /*----------------------------------------------------------- * Port specific definitions. @@ -66,42 +65,44 @@ extern "C" { */ /* Type definitions. */ -#define portCHAR char -#define portFLOAT float -#define portDOUBLE double -#define portLONG long -#define portSHORT short -#define portSTACK_TYPE uint32_t -#define portBASE_TYPE long - -typedef portSTACK_TYPE StackType_t; -typedef long BaseType_t; -typedef unsigned long UBaseType_t; - -#if( configUSE_16_BIT_TICKS == 1 ) - typedef uint16_t TickType_t; - #define portMAX_DELAY ( TickType_t ) 0xffff -#else - typedef uint32_t TickType_t; - #define portMAX_DELAY ( TickType_t ) 0xffffffffUL -#endif + #define portCHAR char + #define portFLOAT float + #define portDOUBLE double + #define portLONG long + #define portSHORT short + #define portSTACK_TYPE uint32_t + #define portBASE_TYPE long + + typedef portSTACK_TYPE StackType_t; + typedef long BaseType_t; + typedef unsigned long UBaseType_t; + + #if ( configUSE_16_BIT_TICKS == 1 ) + typedef uint16_t TickType_t; + #define portMAX_DELAY ( TickType_t ) 0xffff + #else + typedef uint32_t TickType_t; + #define portMAX_DELAY ( TickType_t ) 0xffffffffUL + #endif /*-----------------------------------------------------------*/ +/* Requires definition of UBaseType_t */ + #include "fake_port.h" + /* Hardware specifics. */ -#define portSTACK_GROWTH ( -1 ) -#define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ ) -#define portBYTE_ALIGNMENT 8 -#define portYIELD() -#define portNOP() + #define portSTACK_GROWTH ( -1 ) + #define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ ) + #define portBYTE_ALIGNMENT 8 + #define portNOP() __asm volatile ( "NOP" ) /* * These define the timer to use for generating the tick interrupt. * They are put in this file so they can be shared between "port.c" * and "portisr.c". */ -#define portTIMER_REG_BASE_PTR -#define portTIMER_CLK_ENABLE_BIT -#define portTIMER_AIC_CHANNEL + #define portTIMER_REG_BASE_PTR + #define portTIMER_CLK_ENABLE_BIT + #define portTIMER_AIC_CHANNEL /*-----------------------------------------------------------*/ /* Task utilities. */ @@ -113,37 +114,39 @@ typedef unsigned long UBaseType_t; * THUMB mode code will result in a compile time error. */ -#define portRESTORE_CONTEXT() + #define portRESTORE_CONTEXT() /*-----------------------------------------------------------*/ -#define portSAVE_CONTEXT() - -#define portYIELD_FROM_ISR() + #define portSAVE_CONTEXT() + #define portYIELD() vFakePortYield() + #define portYIELD_WITHIN_API() vFakePortYieldWithinAPI() + #define portYIELD_FROM_ISR() vFakePortYieldFromISR() /* Critical section handling. */ - -#define portDISABLE_INTERRUPTS() - -#define portENABLE_INTERRUPTS() - - -extern void vPortEnterCritical( void ); -extern void vPortExitCritical( void ); - -#define portENTER_CRITICAL() -#define portEXIT_CRITICAL() - -#undef portUSING_MPU_WRAPPERS + #define portDISABLE_INTERRUPTS() vFakePortDisableInterrupts() + #define portENABLE_INTERRUPTS() vFakePortEnableInterrupts() + #define portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) \ + vFakePortClearInterruptMaskFromISR( x ) + #define portSET_INTERRUPT_MASK_FROM_ISR() \ + ulFakePortSetInterruptMaskFromISR() + #define portSET_INTERRUPT_MASK() ulFakePortSetInterruptMask() + #define portCLEAR_INTERRUPT_MASK( x ) vFakePortClearInterruptMask( x ) + #define portASSERT_IF_INTERRUPT_PRIORITY_INVALID() \ + vFakePortAssertIfInterruptPriorityInvalid() + + #define portENTER_CRITICAL() vFakePortEnterCriticalSection() + #define portEXIT_CRITICAL() vFakePortExitCriticalSection() + + #undef portUSING_MPU_WRAPPERS /*-----------------------------------------------------------*/ /* Task function macros as described on the FreeRTOS.org WEB site. */ -#define portTASK_FUNCTION_PROTO( vFunction, pvParameters ) void vFunction( void *pvParameters ) -#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters ) + #define portTASK_FUNCTION_PROTO( vFunction, pvParameters ) void vFunction( void * pvParameters ) + #define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void * pvParameters ) -#ifdef __cplusplus -} -#endif + #ifdef __cplusplus + } + #endif #endif /* PORTMACRO_H */ - diff --git a/FreeRTOS/Test/CMock/doc/Makefile b/FreeRTOS/Test/CMock/doc/Makefile index 7290d2499..5febfd9b1 100644 --- a/FreeRTOS/Test/CMock/doc/Makefile +++ b/FreeRTOS/Test/CMock/doc/Makefile @@ -1,7 +1,10 @@ +include ../makefile.in DOXY_ARGS := "INPUT=../ \n FILE_PATTERNS=*.c *.h\n RECURSIVE=YES\n" DOXY_ARGS += "EXCLUDE=../CMock/ ../config/ ../scripts/ ../build/\n" +DOXY_ARGS += "ALIASES+=\"coverage=\\xrefitem coverage \\\"Coverage Target\\\" \\\"Coverage Targets\\\" \"\n" DOXY_ARGS += "PROJECT_NAME=FreeRTOS Unit Tests\nGENERATE_LATEX=NO\n" DOXY_ARGS += "OUTPUT_DIRECTORY=$(DOC_DIR)" all: - echo -e $(DOXY_ARGS) | doxygen - + mkdir -p $(DOC_DIR) + echo -e $(DOXY_ARGS) | doxygen - diff --git a/FreeRTOS/Test/CMock/lcovrc b/FreeRTOS/Test/CMock/lcovrc new file mode 100644 index 000000000..93028bce6 --- /dev/null +++ b/FreeRTOS/Test/CMock/lcovrc @@ -0,0 +1,3 @@ +lcov_branch_coverage = 1 +genhtml_branch_coverage = 1 +geninfo_intermediate = auto diff --git a/FreeRTOS/Test/CMock/list/Makefile b/FreeRTOS/Test/CMock/list/Makefile index 12dcd9413..f309ead9c 100644 --- a/FreeRTOS/Test/CMock/list/Makefile +++ b/FreeRTOS/Test/CMock/list/Makefile @@ -1,72 +1,41 @@ -# Change according to what your unit test directory is. -# For example if testing queue.c your directory should be called queue -# and the project name should be queue -# if testing list.c your directory should be called list -# and the project name should be list -PROJECT := list +# indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) -# List the dependency files you wish to mock -MOCK_FILES_FP := +# Do not move this line below the include +MAKEFILE_ABSPATH := $(abspath $(lastword $(MAKEFILE_LIST))) +include ../makefile.in +# PROJECT_SRC lists the .c files under test +PROJECT_SRC := list.c -# List the options the compilation would need -CPPFLAGS += -DportUSING_MPU_WRAPPERS=0 +# PROJECT_DEPS_SRC list the .c file that are dependencies of PROJECT_SRC files +# Files in PROJECT_DEPS_SRC are excluded from coverage measurements +PROJECT_DEPS_SRC := -# Try not to edit beyond this line -MOCK_FILES := $(notdir $(MOCK_FILES_FP)) -MOCK_OBJ := $(addprefix mock_,$(MOCK_FILES:.h=.o)) -MOCK_SRC := $(addprefix mock_,$(MOCK_FILES:.h=.c)) -EXEC := $(PROJECT)_utest -PROJECT_DIR := $(abspath .) -SCRATCH_DIR := $(GENERATED_DIR)/$(PROJECT) -PROJ_LIB_DIR := $(SCRATCH_DIR)/lib -MOCK_OBJ_LIST := $(addprefix $(PROJ_LIB_DIR)/,$(MOCK_OBJ)) -MOCKS_DIR := $(SCRATCH_DIR)/mocks -MOCK_SRC_LIST := $(addprefix $(MOCKS_DIR)/,$(MOCK_SRC)) -CFLAGS += -I$(MOCKS_DIR) -COVERAGE_OPTS := -fprofile-arcs -ftest-coverage -fprofile-generate +# PROJECT_HEADER_DEPS: headers that should be excluded from coverage measurements. +PROJECT_HEADER_DEPS := FreeRTOS.h -ifeq ($(MOCK_FILES_FP),) - $(shell mkdir -p $(MOCKS_DIR)) - $(shell touch -a $(MOCKS_DIR)/mock_dummy.c) -endif +# SUITE_UT_SRC: .c files that contain test cases (must end in _utest.c) +SUITE_UT_SRC := list_utest.c -$(MOCKS_DIR)/mock_%.c : Makefile $(PROJECT_DIR)/$(PROJECT).yml | directories - cd $(SCRATCH_DIR) && \ - ruby $(CMOCK_EXEC_DIR)/cmock.rb -o$(PROJECT_DIR)/$(PROJECT).yml \ - $(MOCK_FILES_FP) +# SUITE_SUPPORT_SRC: .c files used for testing that do not contain test cases. +# Paths are relative to PROJECT_DIR +SUITE_SUPPORT_SRC := -$(PROJ_LIB_DIR)/mock_%.o : $(MOCKS_DIR)/mock_%.c - $(CC) -c $< -fPIC $(CFLAGS) -o $@ +# List the headers used by PROJECT_SRC that you would like to mock +MOCK_FILES_FP := -$(BIN_DIR)/$(EXEC) : $(SCRATCH_DIR)/test_runner.o \ - $(SCRATCH_DIR)/$(PROJECT).o \ - $(SCRATCH_DIR)/$(PROJECT)_utest.o \ - $(MOCK_OBJ_LIST) - $(CC) $+ $(LDFLAGS) -o $@ +# List any addiitonal flags needed by the preprocessor +CPPFLAGS += -DportUSING_MPU_WRAPPERS=0 -$(SCRATCH_DIR)/test_runner.o : $(SCRATCH_DIR)/test_runner.c \ - $(SCRATCH_DIR)/$(PROJECT).o - $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ +# List any addiitonal flags needed by the compiler +CFLAGS += -$(SCRATCH_DIR)/$(PROJECT)_utest.o : $(PROJECT_DIR)/$(PROJECT)_utest.c \ - $(MOCKS_DIR)/mock_*.c \ - | directories - $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ +# Try not to edit beyond this line unless necessary. -$(SCRATCH_DIR)/$(PROJECT).o : $(KERNEL_DIR)/$(PROJECT).c - $(CC) -c $< $(CPPFLAGS) $(CFLAGS) $(COVERAGE_OPTS) -o $@ +# Project is determined based on path: $(UT_ROOT_DIR)/$(PROJECT) +PROJECT := $(lastword $(subst /, ,$(dir $(abspath $(MAKEFILE_ABSPATH))))) -$(SCRATCH_DIR)/test_runner.c : $(SCRATCH_DIR)/$(PROJECT)_utest.o \ - Makefile | directories - ruby $(UNITY_BIN_DIR)/generate_test_runner.rb $(EXEC).c \ - $(PROJECT_DIR)/$(PROJECT).yml $@ +export -.PHONY: directories -directories : - -mkdir $(SCRATCH_DIR) - -mkdir $(MOCKS_DIR) - -mkdir $(PROJ_LIB_DIR) - -# prevent deletion by chain of implicit rules -NO_DELETE: $(MOCK_SRC_LIST) +include ../testdir.mk diff --git a/FreeRTOS/Test/CMock/list/list_utest.c b/FreeRTOS/Test/CMock/list/list_utest.c index 28e170b90..8a379f2f0 100644 --- a/FreeRTOS/Test/CMock/list/list_utest.c +++ b/FreeRTOS/Test/CMock/list/list_utest.c @@ -115,8 +115,9 @@ static void validate_empty_list( const List_t * const pxList ) /*! * @brief validate the initilization function of a list + * @coverage vListInitialise */ -void test_vListInitialisee_Success( void ) +void test_vListInitialise_Success( void ) { List_t pxList; @@ -126,6 +127,7 @@ void test_vListInitialisee_Success( void ) /*! * @brief validate the initializatiom function of a list item + * @coverage vListInitialiseItem */ void test_vListInitialiseItem_Sucess( void ) { @@ -138,6 +140,7 @@ void test_vListInitialiseItem_Sucess( void ) /*! * @brief test vListIntertEnd successful case with only 1 item * @details This test ensures the list is sane when 1 item is inserted + * @coverage vListInsertEnd */ void test_vListInsertEnd_Success_1_item( void ) { @@ -166,6 +169,7 @@ void test_vListInsertEnd_Success_1_item( void ) /*! * @brief test vListIntertEnd successful case with only 2 items * @details This test ensures the list is sane when 2 items are inserted + * @coverage vListInsertEnd */ void test_vListInsertEnd_Success_2_items( void ) { @@ -199,6 +203,7 @@ void test_vListInsertEnd_Success_2_items( void ) /*! * @brief test vListIntertEnd successful case with only 3 items * @details This test ensures the list is sane when 3 items are inserted + * @coverage vListInsertEnd */ void test_vListInsertEnd_Success_3_items( void ) { @@ -238,6 +243,7 @@ void test_vListInsertEnd_Success_3_items( void ) /*! * @brief test vListIntertEnd successful case with multiple items (5000) * @details This test ensures the list is sane when 5000 items are inserted + * @coverage vListInsertEnd */ void test_vListInsertEnd_success_multiple_items( void ) { @@ -284,8 +290,9 @@ void test_vListInsertEnd_success_multiple_items( void ) /*! * @brief test vListIntert successful case with 1 item * @details This test ensures the list is sane when 1 item is inserted + * @coverage vListInsert */ -void test_vListInsert_sucess_1_item( void ) +void test_vListInsert_success_1_item( void ) { List_t pxList; ListItem_t pxNewListItem; @@ -311,6 +318,7 @@ void test_vListInsert_sucess_1_item( void ) /*! * @brief test vListIntert successful case with 2 items * @details This test ensures the list is sane when 2 items are inserted + * @coverage vListInsert */ void test_vListInsert_sucess_2_items( void ) { @@ -346,6 +354,7 @@ void test_vListInsert_sucess_2_items( void ) /*! * @brief test vListIntert successful case with 3 items * @details This test ensures the list is sane when 3 items are inserted + * @coverage vListInsert */ void test_vListInsert_sucess_3_items( void ) { @@ -386,6 +395,7 @@ void test_vListInsert_sucess_3_items( void ) /*! * @brief test vListIntert successful case with multiple items (5000) * @details This test ensures the list is sane when multiple items are inserted + * @coverage vListInsert */ void test_vListInsert_success_multiple_items( void ) { @@ -433,6 +443,7 @@ void test_vListInsert_success_multiple_items( void ) /*! * @brief test uxListRemove successful case with 1 item * @details This test ensures the list is sane when 1 item is removed + * @coverage vListInsert */ void test_vListInsert_success_vportMAXDELAY( void ) { @@ -468,6 +479,7 @@ void test_vListInsert_success_vportMAXDELAY( void ) /*! * @brief test uxListRemove successful case with 1 item * @details This test ensures the list is sane when 1 item is removed + * @coverage uxListRemove */ void test_uxListRemove_sucesss( void ) { @@ -505,6 +517,7 @@ void test_uxListRemove_sucesss( void ) /*! * @brief test uxListRemove successful case with 2 items * @details This test ensures the list is sane when 2 items are removed + * @coverage uxListRemove */ void test_uxListRemove_multiple( void ) { @@ -590,6 +603,7 @@ void test_uxListRemove_multiple( void ) * being removed * @details This test ensures that the function uxListRemove reassigns the * index to the previous element of the one being removed + * @coverage uxListRemove */ void test_uxListRemove_index_item( void ) { diff --git a/FreeRTOS/Test/CMock/makefile.in b/FreeRTOS/Test/CMock/makefile.in index 41e5ab147..17ab96cb6 100644 --- a/FreeRTOS/Test/CMock/makefile.in +++ b/FreeRTOS/Test/CMock/makefile.in @@ -1,65 +1,68 @@ -# Various directory locations +# indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) -UT_ROOT_DIR_REL :=. -UT_ROOT_DIR := $(abspath $(UT_ROOT_DIR_REL)) +# Various directories +# Note: for this to work in subdirectories, this makefile must be the first one included. +UT_ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -BUILD_DIR := $(UT_ROOT_DIR)/build -DOC_DIR := $(BUILD_DIR)/doc -COVERAGE_DIR := $(BUILD_DIR)/coverage -BIN_DIR := $(BUILD_DIR)/bin -GENERATED_DIR := $(BUILD_DIR)/generated -LIB_DIR := $(BUILD_DIR)/lib +BUILD_DIR := $(UT_ROOT_DIR)/build +DOC_DIR := $(BUILD_DIR)/doc +COVERAGE_DIR := $(BUILD_DIR)/coverage +BIN_DIR := $(BUILD_DIR)/bin +GENERATED_DIR := $(BUILD_DIR)/generated +LIB_DIR := $(BUILD_DIR)/lib +TOOLS_DIR := $(UT_ROOT_DIR)/tools +FREERTOS_DIR := $(abspath $(UT_ROOT_DIR)../../../FreeRTOS) +KERNEL_DIR := $(abspath $(UT_ROOT_DIR)/../../../FreeRTOS/Source) -FREERTOS_DIR_REL := ../../../FreeRTOS -FREERTOS_DIR := $(abspath $(FREERTOS_DIR_REL)) +CMOCK_DIR := $(UT_ROOT_DIR)/CMock +CMOCK_SRC_DIR := $(CMOCK_DIR)/src +UNITY_DIR := $(CMOCK_DIR)/vendor/unity +UNITY_SRC_DIR := $(UNITY_DIR)/src +UNITY_INC_DIR := $(UNITY_DIR)/src +UNITY_BIN_DIR := $(UNITY_DIR)/auto +UNITY_EXTRAS_DIR := $(UNITY_DIR)/extras +UNITY_MEM_DIR := $(UNITY_EXTRAS_DIR)/memory/src +C_EXCEPTION_SRC_DIR := $(CMOCK_DIR)/vendor/c_exception/lib +CMOCK_EXEC_DIR := $(CMOCK_DIR)/lib -KERNEL_DIR_REL := ../../../FreeRTOS/Source -KERNEL_DIR := $(abspath $(KERNEL_DIR_REL)) +# Include directories +INCLUDE_DIR := -I$(KERNEL_DIR)/include +INCLUDE_DIR += -I. +INCLUDE_DIR += -I$(UT_ROOT_DIR)/config +INCLUDE_DIR += -I$(UNITY_INC_DIR) +INCLUDE_DIR += -I$(CMOCK_SRC_DIR) +INCLUDE_DIR += -I$(UNITY_MEM_DIR) +INCLUDE_DIR += -I$(C_EXCEPTION_SRC_DIR) -CMOCK_DIR := $(UT_ROOT_DIR)/CMock -CMOCK_SRC_DIR := $(CMOCK_DIR)/src -UNITY_DIR := $(CMOCK_DIR)/vendor/unity -UNITY_SRC_DIR := $(UNITY_DIR)/src -UNITY_INC_DIR := $(UNITY_DIR)/src -UNITY_BIN_DIR := $(UNITY_DIR)/auto -UNITY_EXTRAS_DIR := $(UNITY_DIR)/extras -UNITY_MEM_SRC_DIR := $(UNITY_EXTRAS_DIR)/memory/src +# Preprocessor flags +CPPFLAGS := -CMOCK_EXEC_DIR := $(CMOCK_DIR)/lib +# Compiler flags +CFLAGS := $(INCLUDE_DIR) -O0 -ggdb -pthread --std=c99 -Werror -Wall +CFLAGS += -fsanitize=address,undefined -fsanitize-recover=address +CFLAGS += -fstack-protector-all +CFLAGS += -Wformat -Werror=format-security -Werror=array-bounds +CFLAGS += -D_FORTIFY_SOURCE=2 -# Include directory location -INCLUDE_DIR := -I$(KERNEL_DIR)/include -I. -I$(UT_ROOT_DIR)/config -INCLUDE_DIR += -I$(UNITY_INC_DIR) -INCLUDE_DIR += -I$(CMOCK_SRC_DIR) -INCLUDE_DIR += -I$(UNITY_MEM_SRC_DIR) +# Linker flags +LDFLAGS := -L$(LIB_DIR) -Wl,-rpath,$(LIB_DIR) +LDFLAGS += -fsanitize=address,undefined +LDFLAGS += -pthread +LDFLAGS += -lunity +LDFLAGS += -lunitymemory +LDFLAGS += -lcexception +LDFLAGS += -lcmock +LDFLAGS += -lgcov -CPPFLAGS := -CFLAGS := $(INCLUDE_DIR) -O0 -ggdb -pthread --std=c99 -Werror -Wall -CFLAGS += -fsanitize=address,undefined -fsanitize-recover=address -CFLAGS += -fstack-protector-all -CFLAGS += -Wformat -Werror=format-security -Werror=array-bounds -CFLAGS += -D_FORTIFY_SOURCE=2 +# Shared libraries +LIBS := libunity +LIBS += libcmock +LIBS += libunitymemory +LIBS += libcexception -LDFLAGS := -L$(LIB_DIR) -Wl,-rpath,$(LIB_DIR) -LDFLAGS += -fsanitize=address,undefined -LDFLAGS += -pthread -LDFLAGS += -lunity -LDFLAGS += -lunitymemory -LDFLAGS += -lcmock -LDFLAGS += -lgcov +LCOV_OPTS := --config-file $(UT_ROOT_DIR)/lcovrc -export BUILD_DIR -export DOC_DIR -export GENERATED_DIR -export COVERAGE_DIR -export BIN_DIR -export UNITS -export CFLAGS -export LDFLAGS -export CPPFLAGS -export CMOCK_EXEC_DIR -export KERNEL_DIR -export UNITY_BIN_DIR -export LIB_DIR -export UT_ROOT_DIR +# export everything in this file +export diff --git a/FreeRTOS/Test/CMock/queue/Makefile b/FreeRTOS/Test/CMock/queue/Makefile index d5044cc03..34deff580 100644 --- a/FreeRTOS/Test/CMock/queue/Makefile +++ b/FreeRTOS/Test/CMock/queue/Makefile @@ -1,73 +1,14 @@ -# Change according to what your unit test directory is. -# For example if testing queue.c your directory should be called queue -# and the project name should be queue -# if testing list.c your directory should be called list -# and the project name should be list -PROJECT := queue +# Indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) +# Do not move this line below the include +MAKEFILE_ABSPATH := $(abspath $(lastword $(MAKEFILE_LIST))) +include ../makefile.in -# List the dependency files you wish to mock -MOCK_FILES_FP := $(KERNEL_DIR)/include/task.h -MOCK_FILES_FP += $(KERNEL_DIR)/include/list.h -MOCK_FILES_FP += $(UT_ROOT_DIR)/config/fake_assert.h +# SUITES lists the suites contained in subdirectories of this directory +SUITES += two_tests -# List the options the compilation would need -CPPFLAGS += -DportUSING_MPU_WRAPPERS=0 +# PROJECT and SUITE variables are determined based on path like so: +# $(UT_ROOT_DIR)/$(PROJECT)/$(SUITE) +PROJECT := $(lastword $(subst /, ,$(dir $(abspath $(MAKEFILE_ABSPATH))))) -# Try not to edit beyond this line -MOCK_FILES := $(notdir $(MOCK_FILES_FP)) -MOCK_OBJ := $(addprefix mock_,$(MOCK_FILES:.h=.o)) -MOCK_SRC := $(addprefix mock_,$(MOCK_FILES:.h=.c)) -EXEC := $(PROJECT)_utest -PROJECT_DIR := $(abspath .) -SCRATCH_DIR := $(GENERATED_DIR)/$(PROJECT) -PROJ_LIB_DIR := $(SCRATCH_DIR)/lib -MOCK_OBJ_LIST := $(addprefix $(PROJ_LIB_DIR)/,$(MOCK_OBJ)) -MOCKS_DIR := $(SCRATCH_DIR)/mocks -MOCK_SRC_LIST := $(addprefix $(MOCKS_DIR)/,$(MOCK_SRC)) -CFLAGS += -I$(MOCKS_DIR) -COVERAGE_OPTS := -fprofile-arcs -ftest-coverage -fprofile-generate - -ifeq ($(MOCK_FILES_FP),) - $(shell mkdir -p $(MOCKS_DIR)) - $(shell touch -a $(MOCKS_DIR)/mock_dummy.c) -endif - -$(MOCKS_DIR)/mock_%.c : Makefile $(PROJECT_DIR)/$(PROJECT).yml | directories - cd $(SCRATCH_DIR) && \ - ruby $(CMOCK_EXEC_DIR)/cmock.rb -o$(PROJECT_DIR)/$(PROJECT).yml \ - $(MOCK_FILES_FP) - -$(PROJ_LIB_DIR)/mock_%.o : $(MOCKS_DIR)/mock_%.c - $(CC) -c $< -fPIC $(CFLAGS) -o $@ - -$(BIN_DIR)/$(EXEC) : $(SCRATCH_DIR)/test_runner.o \ - $(SCRATCH_DIR)/$(PROJECT).o \ - $(SCRATCH_DIR)/$(PROJECT)_utest.o \ - $(MOCK_OBJ_LIST) - $(CC) $+ $(LDFLAGS) -o $@ - -$(SCRATCH_DIR)/test_runner.o : $(SCRATCH_DIR)/test_runner.c \ - $(SCRATCH_DIR)/$(PROJECT).o - $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ - -$(SCRATCH_DIR)/$(PROJECT)_utest.o : $(PROJECT_DIR)/$(PROJECT)_utest.c \ - $(MOCKS_DIR)/mock_*.c \ - | directories - $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ - -$(SCRATCH_DIR)/$(PROJECT).o : $(KERNEL_DIR)/$(PROJECT).c - $(CC) -c $< $(CPPFLAGS) $(CFLAGS) $(COVERAGE_OPTS) -o $@ - -$(SCRATCH_DIR)/test_runner.c : $(SCRATCH_DIR)/$(PROJECT)_utest.o \ - Makefile | directories - ruby $(UNITY_BIN_DIR)/generate_test_runner.rb $(EXEC).c \ - $(PROJECT_DIR)/$(PROJECT).yml $@ - -.PHONY: directories -directories : - -mkdir $(SCRATCH_DIR) - -mkdir $(MOCKS_DIR) - -mkdir $(PROJ_LIB_DIR) - -# prevent deletion by chain of implicit rules -NO_DELETE: $(MOCK_SRC_LIST) +include ../subdir.mk diff --git a/FreeRTOS/Test/CMock/queue/queue_utest.c b/FreeRTOS/Test/CMock/queue/queue_utest.c index e6f9439a0..9db394f24 100644 --- a/FreeRTOS/Test/CMock/queue/queue_utest.c +++ b/FreeRTOS/Test/CMock/queue/queue_utest.c @@ -42,6 +42,7 @@ #include "mock_task.h" #include "mock_list.h" #include "mock_fake_assert.h" +#include "mock_fake_port.h" /* ============================ GLOBAL VARIABLES =========================== */ @@ -61,7 +62,12 @@ void vPortFree( void * pv ) ******************************************************************************/ void setUp( void ) { + mock_task_Init(); + mock_fake_assert_Init(); + mock_fake_port_Init(); vFakeAssert_Ignore(); + vFakePortEnterCriticalSection_Ignore(); + vFakePortExitCriticalSection_Ignore(); /* Track calls to malloc / free */ UnityMalloc_StartTest(); @@ -71,6 +77,15 @@ void setUp( void ) void tearDown( void ) { UnityMalloc_EndTest(); + + mock_task_Verify(); + mock_task_Destroy(); + + mock_fake_assert_Verify(); + mock_fake_assert_Destroy(); + + mock_fake_port_Verify(); + mock_fake_port_Destroy(); } /*! called at the beginning of the whole suite */ @@ -86,7 +101,7 @@ int suiteTearDown( int numFailures ) /*! * @brief xQueueCreate happy path. - * + * @coverage xQueueGenericCreate */ void test_xQueueCreate_Success( void ) { diff --git a/FreeRTOS/Test/CMock/queue/two_tests/Makefile b/FreeRTOS/Test/CMock/queue/two_tests/Makefile new file mode 100644 index 000000000..1c79a491d --- /dev/null +++ b/FreeRTOS/Test/CMock/queue/two_tests/Makefile @@ -0,0 +1,47 @@ +# Indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) + +# Do not move this line below the include +MAKEFILE_ABSPATH := $(abspath $(lastword $(MAKEFILE_LIST))) +include ../../makefile.in + +# PROJECT_SRC lists the .c files under test +PROJECT_SRC += queue.c + +# PROJECT_DEPS_SRC list the .c file that are dependencies of PROJECT_SRC files +# Files in PROJECT_DEPS_SRC are excluded from coverage measurements +PROJECT_DEPS_SRC += + +# PROJECT_HEADER_DEPS: headers that should be excluded from coverage measurements. +PROJECT_HEADER_DEPS += FreeRTOS.h + +# SUITE_UT_SRC: .c files that contain test cases (must end in _utest.c) +SUITE_UT_SRC += queue_2_utest.c +SUITE_UT_SRC += queue_1_utest.c + +# SUITE_SUPPORT_SRC: .c files used for testing that do not contain test cases. +# Paths are relative to PROJECT_DIR +SUITE_SUPPORT_SRC += + +# List the headers used by PROJECT_SRC that you would like to mock +MOCK_FILES_FP += $(KERNEL_DIR)/include/task.h +MOCK_FILES_FP += $(KERNEL_DIR)/include/list.h +MOCK_FILES_FP += $(UT_ROOT_DIR)/config/fake_assert.h +MOCK_FILES_FP += $(UT_ROOT_DIR)/config/fake_port.h + +# List any addiitonal flags needed by the preprocessor +CPPFLAGS += -DportUSING_MPU_WRAPPERS=0 + +# List any addiitonal flags needed by the compiler +CFLAGS += + +# Try not to edit beyond this line unless necessary. + +# Project / Suite are determined based on path: $(UT_ROOT_DIR)/$(PROJECT)/$(SUITE) +PROJECT := $(lastword $(subst /, ,$(dir $(abspath $(MAKEFILE_ABSPATH)/../)))) +SUITE := $(lastword $(subst /, ,$(dir $(MAKEFILE_ABSPATH)))) + +# Make variables available to included makefile +export + +include ../../testdir.mk diff --git a/FreeRTOS/Test/CMock/queue/two_tests/queue_1_utest.c b/FreeRTOS/Test/CMock/queue/two_tests/queue_1_utest.c new file mode 120000 index 000000000..50aba69ca --- /dev/null +++ b/FreeRTOS/Test/CMock/queue/two_tests/queue_1_utest.c @@ -0,0 +1 @@ +../queue_utest.c
\ No newline at end of file diff --git a/FreeRTOS/Test/CMock/queue/two_tests/queue_2_utest.c b/FreeRTOS/Test/CMock/queue/two_tests/queue_2_utest.c new file mode 120000 index 000000000..50aba69ca --- /dev/null +++ b/FreeRTOS/Test/CMock/queue/two_tests/queue_2_utest.c @@ -0,0 +1 @@ +../queue_utest.c
\ No newline at end of file diff --git a/FreeRTOS/Test/CMock/subdir.mk b/FreeRTOS/Test/CMock/subdir.mk new file mode 100644 index 000000000..4da2bb800 --- /dev/null +++ b/FreeRTOS/Test/CMock/subdir.mk @@ -0,0 +1,46 @@ +# Indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) + +# Define directory paths +SCRATCH_DIR := $(GENERATED_DIR)/$(PROJECT) + +LCOV_LIST := $(foreach suite,$(SUITES),$(SCRATCH_DIR)/$(PROJECT)_$(suite).info) +COVINFO := $(GENERATED_DIR)/$(PROJECT).info +COV_REPORT_DIR := $(SCRATCH_DIR)/coverage + +.PHONY: all clean libs run bin lcov zerocoverage lcovhtml + +all: run + +clean: + rm -rf $(SCRATCH_DIR) + rm -f $(BIN_DIR)/$(PROJECT)*_utest + rm -f $(COVINFO) + +libs: + make -C $(UT_ROOT_DIR) libs + +lcov : $(COVINFO) + +# run each suite and leave gcda / gcov files in place +run: libs + $(foreach suite,$(SUITES),\ + make -C $(suite) run;) + +bin: $(EXEC_LIST) + +zerocoverage: + $(LCOV_BIN_DIR)/lcov --zerocounters --directory $(SCRATCH_DIR) + +# Generate lcov for each suite +$(LCOV_LIST) : + $(foreach suite,$(SUITES),\ + make -C $(suite) lcov;) + +# Combine lcov from each subdirectory into one lcov info file for the project +$(COVINFO) : $(LCOV_LIST) + lcov $(LCOV_OPTS) -o $@ $(foreach cov,$(LCOV_LIST),--add-tracefile $(cov) ) + +lcovhtml : $(COVINFO) + mkdir -p $(COV_REPORT_DIR) + genhtml $(COVINFO) $(LCOV_OPTS) --output-directory $(COV_REPORT_DIR) diff --git a/FreeRTOS/Test/CMock/testdir.mk b/FreeRTOS/Test/CMock/testdir.mk new file mode 100644 index 000000000..df043a224 --- /dev/null +++ b/FreeRTOS/Test/CMock/testdir.mk @@ -0,0 +1,201 @@ +# Indent with spaces +.RECIPEPREFIX := $(.RECIPEPREFIX) $(.RECIPEPREFIX) + +# define the name for this test's output files +ifdef SUITE +EXEC_PREFIX := $(PROJECT)_$(SUITE) +else +EXEC_PREFIX := $(PROJECT) +endif + +# Define directory paths + +ifdef SUITE +PROJECT_DIR := $(abspath ../) +else +PROJECT_DIR := $(abspath .) +endif + +SUITE_DIR := $(abspath .) + +ifdef SUITE +SCRATCH_DIR := $(GENERATED_DIR)/$(PROJECT)/$(SUITE) +else +SCRATCH_DIR := $(GENERATED_DIR)/$(PROJECT) +endif + +MOCKS_DIR := $(SCRATCH_DIR)/mocks +PROJ_DIR := $(SCRATCH_DIR)/proj + +# Define mock details +MOCK_FILES := $(notdir $(MOCK_FILES_FP)) +MOCK_HDR := $(addprefix mock_,$(MOCK_FILES)) +MOCK_SRC := $(addprefix mock_,$(MOCK_FILES:.h=.c)) +MOCK_OBJ := $(addprefix mock_,$(MOCK_FILES:.h=.o)) +MOCK_HDR_LIST := $(addprefix $(MOCKS_DIR)/,$(MOCK_HDR)) +MOCK_SRC_LIST := $(addprefix $(MOCKS_DIR)/,$(MOCK_SRC)) +MOCK_OBJ_LIST := $(addprefix $(SCRATCH_DIR)/,$(MOCK_OBJ)) +CFLAGS += -I$(MOCKS_DIR) + +# Kernel files under test +PROJ_SRC_LIST := $(addprefix $(KERNEL_DIR)/,$(PROJECT_SRC)) +PROJ_PP_LIST := $(addprefix $(PROJ_DIR)/,$(PROJECT_SRC:.c=.i)) +PROJ_OBJ_LIST := $(addprefix $(PROJ_DIR)/,$(PROJECT_SRC:.c=.o)) +PROJ_GCDA_LIST := $(PROJ_OBJ_LIST:.o=.gcda) + +# Unit test files +SUITE_OBJ_LIST := $(addprefix $(SCRATCH_DIR)/,$(SUITE_UT_SRC:.c=.o)) +RUNNER_SRC_LIST := $(addprefix $(SCRATCH_DIR)/,$(SUITE_UT_SRC:_utest.c=_utest_runner.c)) +RUNNER_OBJ_LIST := $(addprefix $(SCRATCH_DIR)/,$(SUITE_UT_SRC:_utest.c=_utest_runner.o)) + +# Support files +SF_OBJ_LIST := $(addprefix $(SCRATCH_DIR)/sf_,$(SUITE_SUPPORT_SRC:.c=.o)) +DEPS_OBJ_LIST := $(addprefix $(SCRATCH_DIR)/dep_,$(PROJECT_DEPS_SRC:.c=.o)) +EXECS := $(addprefix $(EXEC_PREFIX)_,$(SUITE_UT_SRC:.c=)) +EXEC_LIST := $(addprefix $(BIN_DIR)/,$(EXECS)) +LCOV_LIST := $(addsuffix .info,$(addprefix $(SCRATCH_DIR)/,$(SUITE_UT_SRC:.c=))) +COVINFO := $(abspath $(SCRATCH_DIR)/..)/$(EXEC_PREFIX).info +LIBS_LIST := $(foreach lib, $(LIBS), $(LIB_DIR)/$(lib).so) + +# Coverage related options +GCC_COV_OPTS := -fprofile-arcs -ftest-coverage -fprofile-generate +GCOV_OPTS := --unconditional-branches --branch-probabilities +COV_REPORT_DIR := $(SCRATCH_DIR)/coverage + +.PHONY: all clean run gcov bin lcov lcovhtml libs + +# Prevent deletion of intermediate files +NO_DELETE : $(MOCK_HDR_LIST) $(MOCK_SRC_LIST) $(MOCK_OBJ_LIST) \ + $(DEPS_OBJ_LIST) $(SF_OBJ_LIST) $(EXEC_LIST) \ + $(PROJ_PP_LIST) $(PROJ_OBJ_LIST) $(PROJ_GCDA_LIST) \ + $(SUITE_OBJ_LIST) $(RUNNER_SRC_LIST) $(RUNNER_OBJ_LIST) \ + $(COVINFO) $(LCOV_LIST) + +# Cases that run test binaries cannot be run in parallel. +.NOTPARALLEL : $(COVINFO) $(LCOV_LIST) $(PROJ_GCDA_LIST) + +.DEFAULT_GOAL := run + +# Generate gcov files by default +run : gcov + +gcov : $(PROJ_GCDA_LIST) + +clean: + rm -rf $(SCRATCH_DIR) + rm -rf $(EXEC_LIST) + rm -rf $(COVINFO) + +$(LIBS_LIST) : + make -C $(UT_ROOT_DIR) libs + +define run-test +$(1) + +endef + +# Run and append to gcov data files +$(PROJ_GCDA_LIST) : $(EXEC_LIST) + rm -f $(PROJ_DIR)/*.gcda + mkdir -p $(BIN_DIR) + # run each test case + $(foreach bin,$^,$(call run-test,$(bin))) + +# Run and generate lcov +lcov: $(COVINFO) + +lcovhtml : $(COVINFO) + mkdir -p $(COV_REPORT_DIR) + genhtml $(COVINFO) $(LCOV_OPTS) --output-directory $(COV_REPORT_DIR) + +bin: $(EXEC_LIST) + +# Generate _mock.c / .h files +$(MOCK_HDR_LIST) $(MOCK_SRC_LIST) : $(PROJECT_DIR)/$(PROJECT).yml $(MOCK_FILES_FP) + mkdir -p $(SCRATCH_DIR) $(MOCKS_DIR) + cd $(SCRATCH_DIR) && \ + ruby $(CMOCK_EXEC_DIR)/cmock.rb -o$(PROJECT_DIR)/$(PROJECT).yml \ + $(MOCK_FILES_FP) + +# Generate callgraph for coverage filtering +$(PROJ_DIR)/callgraph.json : $(PROJ_SRC_LIST) + mkdir -p $(PROJ_DIR) + python3 $(UT_ROOT_DIR)/tools/callgraph.py $^ > $@ + +# preprocess proj files to expand macros for coverage +$(PROJ_DIR)/%.i : $(KERNEL_DIR)/%.c + mkdir -p $(PROJ_DIR) + $(CC) -E $< $(CPPFLAGS) $(CFLAGS) -o $@ + +# compile the project objects with coverage instrumented +$(PROJ_DIR)/%.o : $(PROJ_DIR)/%.i + $(CC) -c $< $(CPPFLAGS) $(INCLUDE_DIR) $(GCC_COV_OPTS) -o $@ + +# Build mock objects +$(SCRATCH_DIR)/mock_%.o : $(MOCKS_DIR)/mock_%.c + $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ + +# compile unit tests +$(SCRATCH_DIR)/%_utest.o : $(SUITE_DIR)/%_utest.c $(MOCK_HDR_LIST) $(LIBS_LIST) + mkdir -p $(SCRATCH_DIR) + $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ + +# compile support files +$(SCRATCH_DIR)/sf_%.o : $(PROJECT_DIR)/%.c $(MOCK_HDR_LIST) + mkdir -p $(SCRATCH_DIR) + $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ + +# compile c files that are needed by PROJ but not mocked +$(SCRATCH_DIR)/dep_%.o : $(KERNEL_DIR)/%.c + mkdir -p $(SCRATCH_DIR) + $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ + +# generate a test runner for each test file +$(SCRATCH_DIR)/%_utest_runner.c : $(SUITE_DIR)/%_utest.c + mkdir -p $(SCRATCH_DIR) + ruby $(UNITY_BIN_DIR)/generate_test_runner.rb\ + $(PROJECT_DIR)/$(PROJECT).yml $< $@ + +# compile test runner +$(SCRATCH_DIR)/%_utest_runner.o : $(SCRATCH_DIR)/%_utest_runner.c + mkdir -p $(SCRATCH_DIR) + $(CC) -c $< $(CPPFLAGS) $(CFLAGS) -o $@ + +# Link the _utest binary +$(EXEC_LIST) : $(BIN_DIR)/$(EXEC_PREFIX)_%_utest : $(SCRATCH_DIR)/%_utest.o \ + $(SCRATCH_DIR)/%_utest_runner.o \ + $(SF_OBJ_LIST) $(MOCK_OBJ_LIST) \ + $(PROJ_OBJ_LIST) $(LIBS_LIST) \ + $(DEPS_OBJ_LIST) + mkdir -p $(BIN_DIR) + $(CC) $< $(subst .o,_runner.o,$<) $(SF_OBJ_LIST) $(DEPS_OBJ_LIST) \ + $(MOCK_OBJ_LIST) $(PROJ_OBJ_LIST) $(LDFLAGS) -o $@ + +# Run the test runner and genrate a filtered gcov.json.gz file +$(SCRATCH_DIR)/%_utest.info : $(BIN_DIR)/$(EXEC_PREFIX)_%_utest \ + $(PROJ_DIR)/callgraph.json + # Remove any existing coverage data + rm -f $(PROJ_DIR)/*.gcda + + # run the testrunner + $< + + # Gather coverage into a json.gz file + gcov $(GCOV_OPTS) $(foreach src,$(PROJECT_SRC),$(PROJ_DIR)/$(src:.c=.gcda)) \ + --json-format --stdout | gzip > $(subst .info,.json.gz,$@) + + # Filter coverage based on tags in unit test file + $(TOOLS_DIR)/filtercov.py --in $(subst .info,.json.gz,$@) \ + --map $(PROJ_DIR)/callgraph.json \ + --test $(SUITE_DIR)/$*_utest.c \ + --format lcov \ + --out $@ + -lcov $(LCOV_OPTS) --summary $@ + + # Remove temporary files + rm -f $(subst .info,.json.gz,$@) + rm -f $(PROJ_GCDA_LIST) + +# Combine lcov from each test bin into one lcov info file for the suite +$(COVINFO) : $(LCOV_LIST) + lcov $(LCOV_OPTS) -q -o $@ $(foreach cov,$(LCOV_LIST),--add-tracefile $(cov) ) diff --git a/FreeRTOS/Test/CMock/tools/callgraph.py b/FreeRTOS/Test/CMock/tools/callgraph.py new file mode 100755 index 000000000..7c3f57d32 --- /dev/null +++ b/FreeRTOS/Test/CMock/tools/callgraph.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +############################################################################### +# FreeRTOS +# Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# 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. +# +# https://www.FreeRTOS.org +# https://github.com/FreeRTOS +############################################################################### +import json +import os +import re +import subprocess +import sys +from typing import Dict, List, Set + +target_files = sys.argv[1:] + +for f in target_files: + if not os.path.isfile(f): + print("ERROR: Input file {} does not exist.".format(f)) + exit(1) + +ret = subprocess.run( + [ + "cflow", + "--print-level", + "--no-main", + "--omit-arguments", + "--omit-symbol-names", + "--all", + ] + + target_files, + capture_output=True, +) + +lineregex = ( + r"^{\s*(?P<level>\d+)} \s*" + r"(?P<function>\S*)\(\) \<.* at " + r"(?P<filename>.*):\d+\>(:)?" + r"(?P<xref> \[see \d+\])?$" +) +linepattern = re.compile(lineregex) +parent_stack = [""] +last_indent_level = 0 +last_function_name = "" +callmap: Dict[str, Set[str]] = {} +callmap[""] = set() + +for line in ret.stdout.decode("utf-8").splitlines(): + match = linepattern.match(line) + # Check that the function for this line is in a target file + if match and (match.group("filename") in target_files): + indent_level = int(match.group("level")) + function_name = match.group("function") + + # Add an entry for the current function + if function_name not in callmap: + callmap[function_name] = set() + + # Indent -> lower in the call stack + if indent_level > last_indent_level: + # add last function to the stack + parent_stack.append(last_function_name) + + # Outdent -> higher in the call stack + elif last_indent_level > indent_level: + de_indent_steps = last_indent_level - indent_level + # De-indent = pop off the stack + for _i in range(0, de_indent_steps): + parent_stack.pop() + + # Update parent function(s) dependency list + for parent in parent_stack: + callmap[parent].add(function_name) + + last_function_name = function_name + last_indent_level = indent_level + +# remove zero-level fake parent +callmap.pop("") + +callmap_list: Dict[str, List[str]] = {} +# convert sets to lists for json output +for key in callmap: + temp_list = list(callmap[key]) + callmap_list[key] = temp_list + +print(json.dumps(callmap_list)) diff --git a/FreeRTOS/Test/CMock/tools/filtercov.py b/FreeRTOS/Test/CMock/tools/filtercov.py new file mode 100755 index 000000000..3b1fc120f --- /dev/null +++ b/FreeRTOS/Test/CMock/tools/filtercov.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +############################################################################### +# FreeRTOS +# Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# 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. +# +# https://www.FreeRTOS.org +# https://github.com/FreeRTOS +############################################################################### +import argparse +import gzip +import json +import os +import re +import sys + + +def main(): + arg_parser = argparse.ArgumentParser( + description="Filter the contents of a gov intermediate coverage file" + ) + arg_parser.add_argument( + "-i", "--in", required=True, help="input .gcov.json.gz file to be filtered" + ) + arg_parser.add_argument( + "-m", + "--map", + required=True, + help="Json file containing a callmap for the files under test", + ) + arg_parser.add_argument( + "-t", + "--test", + required=True, + help="The unit test .c file which contains @coverage flags.", + ) + arg_parser.add_argument( + "-f", "--format", required=True, help="The output format to use (json|lcov)" + ) + arg_parser.add_argument( + "-o", + "--out", + required=False, + help="output file path (otherwise stdout out is used)", + ) + + args = arg_parser.parse_args() + # Validate arguments + if not os.path.isfile(vars(args)["in"]): + print("The input file path does not exist.", file=sys.stderr) + sys.exit(1) + + if vars(args)["out"] and not os.path.isdir(os.path.dirname(vars(args)["out"])): + print("The output directory does not exist.", file=sys.stderr) + sys.exit(1) + + if not os.path.isfile(args.map): + print("The callmap file path does not exist.", file=sys.stderr) + sys.exit(1) + + if not os.path.isfile(args.test): + print("The test file path does not exist.", file=sys.stderr) + sys.exit(1) + + tagged_functions = get_tagged_functions_in_file(args.test) + if len(tagged_functions) == 0: + print("No target functions found in test file.") + sys.exit(1) + print("Target functions from UT: " + str(tagged_functions), file=sys.stderr) + + cov_functions_deps = get_function_deps(args.map, tagged_functions) + print( + "Target functions and dependencies: " + str(cov_functions_deps), file=sys.stderr + ) + + if vars(args)["in"] and (".gz" in vars(args)["in"]): + covfile_handle = gzip.open(vars(args)["in"], "rb") + else: + covfile_handle = open(vars(args)["in"], "r") + covdata_out = filter_coverage_file(covfile_handle, cov_functions_deps) + covfile_handle.close() + + filter_excluded_lines(covdata_out) + + # default to lcov output format + out_format = "lcov" + if vars(args)["format"]: + out_format = args.format + + if vars(args)["out"]: + if ".gz" in args.out: + outfile = gzip.open(args.out, "wb", encoding="ascii") + else: + outfile = open(args.out, "w") + else: + outfile = sys.stdout + if out_format == "json": + json.dump(covdata_out, outfile) + elif out_format == "lcov": + convert_to_lcov_info(args, covdata_out, outfile) + + if outfile is not sys.stdout: + outfile.close() + + +def get_tagged_functions_in_file(filename): + """Given a filename, return a set of the target function names tagged with + @coverage in that file.""" + token_regex = r"^.*@coverage(( (\w+))+).*$" + token_pattern = re.compile(token_regex, re.IGNORECASE) + + cov_functions = set() + + line = "" + with open(filename, "r") as f: + while True: + line = f.readline() + if not line: + break + match = re.match(token_pattern, line) + if match: + loc = match.group(1).strip().split() + for i in loc: + cov_functions.add(i) + return cov_functions + + +def get_function_deps(call_map_file, cov_functions): + """Return a set of functions and the functions called by those functions. + Given a set of function names, return a set containing those functions and + any dependent functions defined by the callmap.""" + cov_functions_deps = set() + with open(call_map_file, "r") as callmap_f: + callmap = json.load(callmap_f) + for function in cov_functions: + # Check callgraph and add dependent functions in this project + if function in callmap: + cov_functions_deps.add(function) + for dep in callmap[function]: + cov_functions_deps.add(dep) + else: + print( + "WARN: function specified in unit test file ({}) was not " + "found in {}.\nCheck that the function name is correct and" + " that the specified coverage target is not a macro.".format( + function, call_map_file + ), + file=sys.stderr, + ) + return cov_functions_deps + + +def filter_coverage_file(covfile_handle, cov_functions): + """Given an input coverage json file and a set of functions that the test + is targeting, filter the coverage file to only include data generated + by running the given functions.""" + covdata_out = dict() + covdata = json.load(covfile_handle) + # copy basic info + assert covdata["format_version"] == "1" + covdata_out["format_version"] = covdata["format_version"] + covdata_out["current_working_directory"] = covdata["current_working_directory"] + covdata_out["data_file"] = covdata["data_file"] + covdata_out["gcc_version"] = covdata["gcc_version"] + # handle per proj file data + covdata_out["files"] = list() + for targetfile in covdata["files"]: + cur_file = dict() + cur_file["file"] = targetfile["file"] + cur_functions = list() + for function in targetfile["functions"]: + if function["name"] in cov_functions: + cur_functions.append(function) + cur_file["functions"] = cur_functions + cur_lines = list() + for line in targetfile["lines"]: + if line["function_name"] in cov_functions: + cur_lines.append(line) + cur_file["lines"] = cur_lines + covdata_out["files"].append(cur_file) + return covdata_out + + +def get_excluded_lines(c_file): + """Read a .c file and determine which lines should be excluded based on + LCOV_EXCL comments""" + excl_lines = set() + excl_br_lines = set() + excl_line_flag = False + excl_br_line_flag = False + with open(c_file, "r") as cfile: + line_number = 1 + while True: + line = cfile.readline() + if not line: + break + if "LCOV_EXCL" in line: + if "LCOV_EXCL_LINE" in line: + excl_lines.add(line_number) + if "LCOV_EXCL_BR_LINE" in line: + excl_br_lines.add(line_number) + if "LCOV_EXCL_START" in line: + excl_line_flag = True + if "LCOV_EXCL_STOP" in line: + excl_line_flag = False + if "LCOV_EXCL_BR_START" in line: + excl_br_line_flag = True + if "LCOV_EXCL_BR_STOP" in line: + excl_br_line_flag = False + if excl_line_flag: + excl_lines.add(line_number) + if excl_br_line_flag: + excl_br_lines.add(line_number) + line_number += 1 + return excl_lines, excl_br_lines + + +def filter_excluded_lines(covdata): + """ Remove coverage data for lines excluded with LCOV_EXCL_* comments """ + for target_file in covdata["files"]: + excl_lines, excl_br_lines = get_excluded_lines(target_file["file"]) + target_lines_excl = list() + for target_line in target_file["lines"]: + if not ("line_number" in target_line and "count" in target_line): + continue + if int(target_line["line_number"]) in excl_br_lines: + target_line["branches"] = list() + if int(target_line["line_number"]) not in excl_lines: + target_lines_excl.append(target_line) + target_file["lines"] = target_lines_excl + + +# Based on lcov's geninfo perl script +def convert_to_lcov_info(args, covdata, outfile): + """ Convert a given gcov intermediate format json file to lcov .info format """ + outfile.write("TN:{}\n".format(os.path.basename(args.test.replace(".c", "")))) + for target_file in covdata["files"]: + functions_found = 0 + functions_hit = 0 + outfile.write("SF:{}\n".format(target_file["file"])) + + # Handle function data + for target_func in target_file["functions"]: + # validate stanza and skip + if not ( + "name" in target_func + and "start_line" in target_func + and "execution_count" in target_func + ): + continue + outfile.write( + "FN:{},{}\n".format(target_func["start_line"], target_func["name"]) + ) + + outfile.write( + "FNDA:{},{}\n".format( + target_func["execution_count"], target_func["name"] + ) + ) + functions_found += 1 + if target_func["execution_count"] > 0: + functions_hit += 1 + if functions_found > 0: + outfile.write("FNF:{}\n".format(functions_found)) + outfile.write("FNH:{}\n".format(functions_hit)) + + lines_found = 0 + lines_hit = 0 + branches_found = 0 + branches_hit = 0 + # Handle line data + for target_line in target_file["lines"]: + if not ("line_number" in target_line and "count" in target_line): + continue + outfile.write( + "DA:{},{}\n".format(target_line["line_number"], target_line["count"]) + ) + lines_found += 1 + if target_line["count"] > 0: + lines_hit += 1 + # Branch number within each line + branch_number = 0 + # Handle branch data + for target_branch in target_line["branches"]: + branch_count = "-" + if target_line["unexecuted_block"] or target_line["count"] == 0: + branch_count = "-" + elif "count" in target_branch: + branch_count = target_branch["count"] + # Note: block number is not needed. + outfile.write( + "BRDA:{},0,{},{}\n".format( + target_line["line_number"], branch_number, branch_count + ) + ) + branch_number += 1 + branches_found += 1 + if branch_count != "-": + branches_hit += 1 + if branches_found > 0: + outfile.write("BRF:{}\n".format(branches_found)) + outfile.write("BRH:{}\n".format(branches_hit)) + outfile.write("LF:{}\n".format(lines_found)) + outfile.write("LH:{}\n".format(lines_hit)) + outfile.write("end_of_record\n") + + +if __name__ == "__main__": + main() |