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
pull/525/head
Paul Bartell 4 years ago committed by GitHub
parent c8fa483b68
commit d7e5f40885
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 = [

@ -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

@ -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
```

@ -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 */

@ -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 */

@ -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 -

@ -0,0 +1,3 @@
lcov_branch_coverage = 1
genhtml_branch_coverage = 1
geninfo_intermediate = auto

@ -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

@ -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 )
{

@ -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

@ -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

@ -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 )
{

@ -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

@ -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)

@ -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) )

@ -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))

@ -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()
Loading…
Cancel
Save