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.pypull/525/head
parent
c8fa483b68
commit
d7e5f40885
@ -0,0 +1 @@
|
||||
build/
|
@ -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
|
||||
|
@ -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 */
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 @@
|
||||
../queue_utest.c
|
@ -0,0 +1 @@
|
||||
../queue_utest.c
|
@ -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…
Reference in New Issue