1# SPDX-License-Identifier: GPL-2.0-only 2 3testsrc := $(top)/tests 4 5# Place the build output in one of two places depending on COV, so that code 6# built with code coverage never mixes with code built without code coverage. 7ifeq ($(COV),1) 8testobj := $(obj)/coverage 9else 10testobj := $(obj)/tests 11endif 12coverage-dir := $(testobj)/coverage_reports 13 14cmockasrc := $(coreboottop)/3rdparty/cmocka 15cmockaobj := $(objutil)/cmocka 16CMOCKA_LIB := $(cmockaobj)/src/libcmocka.so 17 18CMAKE := cmake 19 20TEST_DEFAULT_CONFIG := $(top)/configs/config.unit-tests 21TEST_DOTCONFIG := $(testobj)/.config 22TEST_KCONFIG_AUTOHEADER := $(testobj)/libpayload-config.src.h 23TEST_KCONFIG_AUTOCONFIG := $(testobj)/auto.conf 24TEST_KCONFIG_DEPENDENCIES := $(testobj)/auto.conf.cmd 25TEST_KCONFIG_SPLITCONFIG := $(testobj)/config/ 26TEST_KCONFIG_TRISTATE := $(testobj)/tristate.conf 27TEST_KCONFIG_NEGATIVES := 1 28TEST_KBUILD_KCONFIG := $(top)/Kconfig 29TEST_CONFIG_ := CONFIG_LP_ 30 31 32# Default includes 33TEST_CFLAGS := -include include/kconfig.h 34TEST_CFLAGS += -include $(coreboottop)/src/commonlib/bsd/include/commonlib/bsd/compiler.h 35TEST_CFLAGS += -Iinclude -Iinclude/mock 36TEST_CFLAGS += -I$(coreboottop)/src/commonlib/bsd/include 37TEST_CFLAGS += -I$(coreboottop)/src/commonlib/include 38TEST_CFLAGS += -I$(dir $(TEST_KCONFIG_AUTOHEADER)) 39TEST_CFLAGS += -I$(VBOOT_SOURCE)/firmware/include 40 41# Test specific includes 42TEST_CFLAGS += -I$(testsrc)/include 43TEST_CFLAGS += -I$(cmockasrc)/include 44 45# Minimal subset of warnings and errors. Tests can be less strict than actual build. 46TEST_CFLAGS += -Wall -Wundef -Wstrict-prototypes -Wvla 47TEST_CFLAGS += -Wwrite-strings -Wno-trigraphs -Wimplicit-fallthrough 48TEST_CFLAGS += -Wstrict-aliasing -Wshadow -Werror 49TEST_CFLAGS += -Wno-unknown-warning-option -Wno-source-mgr -Wno-main-return-type 50 51TEST_CFLAGS += -std=gnu11 -ffunction-sections -fdata-sections -fno-builtin 52 53ifneq ($(filter-out 0,$(DEBUG)),) 54TEST_CFLAGS += -Og -ggdb3 55else 56TEST_CFLAGS += -Os 57endif 58 59# Make unit-tests detectable by the code 60TEST_CFLAGS += -D__TEST__ 61 62# Link against CMocka 63TEST_LDFLAGS := -L$(dir $(CMOCKA_LIB)) -lcmocka -Wl,-rpath=$(dir $(CMOCKA_LIB)) 64 65TEST_LDFLAGS += -Wl,--gc-sections 66 67# Disable userspace relocations 68TEST_CFLAGS += -fno-pie -fno-pic 69TEST_LDFLAGS += -no-pie 70 71ifeq ($(COV),1) 72TEST_CFLAGS += --coverage 73TEST_LDFLAGS += --coverage 74endif 75 76 77# Extra attributes for unit tests. Declated per each test. Only `srcs` is required. 78attributes := cflags config mocks srcs 79 80alltests := 81subdirs := tests/crypto tests/curses tests/drivers tests/gdb tests/libc tests/libcbfs 82subdirs += tests/liblz4 tests/liblzma tests/libpci 83 84define tests-handler 85alltests += $(1)$(2) 86$(foreach attribute,$(attributes), \ 87 $(eval $(1)$(2)-$(attribute) += $($(2)-$(attribute)))) 88$(foreach attribute,$(attributes), \ 89 $(eval $(2)-$(attribute) := )) 90endef 91 92# Copy attributes of one test to another 93# $1 - input test name 94# $2 - output test name 95copy-test = $(foreach attribute,$(attributes), \ 96 $(eval $(strip $(2))-$(attribute) := $($(strip $(1))-$(attribute)))) 97 98$(call add-special-class,tests) 99$(call evaluate_subdirs) 100 101# Create actual targets for unit test binaries 102# $1 - test name 103define TEST_CC_template 104 105# Generate custom config.h redefining given config symbols, and declaring mocked 106# functions weak. It is important that the compiler already sees that they are 107# weak (and they aren't just turned weak at a later stage) to prevent certain 108# optimizations that would break if the function gets replaced. (For clang this 109# file needs to be marked `system_header` to prevent it from warning about 110# `#pragma weak` entries without a matching function declaration, since there is 111# no -Wno-xxx commandline for that.) 112$(1)-config-file := $(testobj)/$(1)/libpayload-config.h 113$$($(1)-config-file): $(TEST_KCONFIG_AUTOHEADER) 114 mkdir -p $$(dir $$@); 115 printf '// File generated by tests/Makefile.mk\n// Do not change\n' > $$@; 116 printf '#ifndef TEST_LIBPAYLOAD_CONFIG_H_\n' >> $$@; 117 printf '#define TEST_LIBPAYLOAD_CONFIG_H_\n' >> $$@; 118 printf '#include <%s>\n\n' "$(notdir $(TEST_KCONFIG_AUTOHEADER))" >> $$@; 119 for kv in $$($(1)-config); do \ 120 key="`echo $$$$kv | cut -d '=' -f -1`"; \ 121 value="`echo $$$$kv | cut -d '=' -f 2-`"; \ 122 printf '#undef %s\n' "$$$$key" >> $$@; \ 123 printf '#define %s %s\n\n' "$$$$key" "$$$$value" >> $$@; \ 124 done 125 printf '#ifdef __clang__\n' >> $$@; 126 printf '#pragma clang system_header\n' >> $$@; 127 printf '#endif\n\n' >> $$@; 128 printf '#ifdef __TEST_SRCOBJ__\n' >> $$@; 129 for m in $$($(1)-mocks); do \ 130 printf '#pragma weak %s\n' "$$$$m" >> $$@; \ 131 done 132 printf '#endif\n\n' >> $$@; 133 printf '#endif\n' >> $$@; 134 135$($(1)-objs): TEST_CFLAGS += -I$$(dir $$($(1)-config-file)) \ 136 -D__TEST_NAME__=\"$(subst /,_,$(1))\" 137 138# Give us a way to distinguish between libpayload source files and test files in the code. 139$($(1)-srcobjs): TEST_CFLAGS += -D__TEST_SRCOBJ__ 140 141# Compile sources and apply mocking/wrapping for selected symbols. 142# For each listed mock add new symbol with prefix `__real_`, 143# pointing to the same section:address. This will keep original 144# function accessible if required. 145$($(1)-objs): $(testobj)/$(1)/%.o: $$$$*.c $$($(1)-config-file) 146 mkdir -p $$(dir $$@) 147 $(HOSTCC) $$(TEST_CFLAGS) $($(1)-cflags) -MMD \ 148 -MF $$(basename $$@).d -MT $$@ -c $$< -o $$@.orig 149 objcopy_wrap_flags=''; \ 150 for sym in $$($(1)-mocks); do \ 151 sym_line="$$$$($(HOSTOBJDUMP) -t $$@.orig \ 152 | grep -E "[0-9a-fA-F]+\\s+w\\s+F\\s+.*\\s+$$$$sym$$$$")"; \ 153 if [ ! -z "$$$$sym_line" ] ; then \ 154 addr="$$$$(echo "$$$$sym_line" | awk '{ print $$$$1 }')"; \ 155 section="$$$$(echo "$$$$sym_line" | awk '{ print $$$$(NF - 2) }')"; \ 156 objcopy_wrap_flags="$$$$objcopy_wrap_flags --add-symbol __real_$$$${sym}=$$$${section}:0x$$$${addr},function,global"; \ 157 fi \ 158 done ; \ 159 $(HOSTOBJCOPY) $$@.orig $$$$objcopy_wrap_flags $$@ 160 161$($(1)-bin): $($(1)-objs) $(CMOCKA_LIB) 162 $(HOSTCC) $$^ $($(1)-cflags) $$(TEST_LDFLAGS) -o $$@ 163 164endef 165 166$(foreach test,$(alltests), \ 167 $(eval $(test)-srcobjs := $(addprefix $(testobj)/$(test)/, \ 168 $(patsubst %.c,%.o,$(filter-out tests/%,$($(test)-srcs))))) \ 169 $(eval $(test)-objs := $(addprefix $(testobj)/$(test)/, \ 170 $(patsubst %.c,%.o,$($(test)-srcs)))) \ 171 $(eval $(test)-bin := $(testobj)/$(test)/run)) 172$(foreach test,$(alltests), \ 173 $(eval $(call TEST_CC_template,$(test)))) 174$(foreach test,$(alltests), \ 175 $(eval all-test-objs += $($(test)-objs)) \ 176 $(eval test-bins += $($(test)-bin))) 177 178DEPENDENCIES += $(addsuffix .d,$(basename $(all-test-objs))) 179-include $(DEPENDENCIES) 180 181# Build CMocka 182$(CMOCKA_LIB): 183 echo "*** Building CMOCKA ***" 184 mkdir -p $(cmockaobj) 185 cd $(cmockaobj) && $(CMAKE) $(abspath $(cmockasrc)) 186 $(MAKE) -C $(cmockaobj) 187 188# Kconfig targets 189$(TEST_DOTCONFIG): 190 mkdir -p $(dir $@) 191 cp $(TEST_DEFAULT_CONFIG) $(TEST_DOTCONFIG) 192 193$(TEST_KCONFIG_AUTOHEADER): TEST_KCONFIG_FLAGS := DOTCONFIG=$(TEST_DOTCONFIG) \ 194 KCONFIG_AUTOHEADER=$(TEST_KCONFIG_AUTOHEADER) \ 195 KCONFIG_AUTOCONFIG=$(TEST_KCONFIG_AUTOCONFIG) \ 196 KCONFIG_DEPENDENCIES=$(TEST_KCONFIG_DEPENDENCIES) \ 197 KCONFIG_SPLITCONFIG=$(TEST_KCONFIG_SPLITCONFIG) \ 198 KCONFIG_TRISTATE=$(TEST_KCONFIG_TRISTATE) \ 199 KCONFIG_NEGATIVES=$(TEST_KCONFIG_NEGATIVES) \ 200 KBUILD_KCONFIG=$(TEST_KBUILD_KCONFIG) \ 201 KBUILD_DEFCONFIG=$(TEST_DEFAULT_CONFIG) \ 202 CONFIG_=$(TEST_CONFIG_) 203 204$(TEST_KCONFIG_AUTOHEADER): $(TEST_DOTCONFIG) $(objk)/conf 205 mkdir -p $(dir $@) 206 $(MAKE) $(TEST_KCONFIG_FLAGS) olddefconfig V=$(V) 207 $(MAKE) $(TEST_KCONFIG_FLAGS) syncconfig V=$(V) 208 209$(TEST_KCONFIG_AUTOCONFIG): $(TEST_KCONFIG_AUTOHEADER) 210 true 211 212.PHONY: $(alltests) $(addprefix clean-,$(alltests)) $(addprefix try-,$(alltests)) 213.PHONY: $(addprefix build-,$(alltests)) $(addprefix run-,$(alltests)) 214.PHONY: unit-tests build-unit-tests run-unit-tests clean-unit-tests 215.PHONY: junit.xml-unit-tests clean-junit.xml-unit-tests 216 217ifeq ($(JUNIT_OUTPUT),y) 218$(addprefix run-,$(alltests)): export CMOCKA_MESSAGE_OUTPUT=xml 219$(addprefix run-,$(alltests)): export CMOCKA_XML_FILE=$(testobj)/junit-libpayload-%g.xml 220endif 221 222$(addprefix run-,$(alltests)): run-%: $$(%-bin) 223 rm -f $(testobj)/junit-libpayload-$(subst /,_,$(patsubst $(testobj)/%/,%,$(dir $^)))\(*\).xml 224 rm -f $(testobj)/$(subst /,_,$^).failed 225 -$^ || echo failed > $(testobj)/$(subst /,_,$^).failed 226 227$(addprefix build-,$(alltests)): build-%: $$(%-bin) 228 229$(alltests): run-$$(@) 230 231$(addprefix try-,$(alltests)): try-%: clean-% $(CMOCKA_LIB) $(TEST_KCONFIG_AUTOCONFIG) 232 mkdir -p $(testobj)/$* 233 echo "<testcase classname='libpayload_build_unit_test' name='$*'>" >> $(testobj)/$*.tmp; \ 234 $(MAKE) V=$(V) Q=$(Q) COV=$(COV) JUNIT_OUTPUT=y "build-$*" >> $(testobj)/$*.tmp.2 2>&1 \ 235 && type="system-out" || type="failure"; \ 236 if [ $$type = "failure" ]; then \ 237 echo "<failure type='buildFailed'>" >> $(testobj)/$*.tmp; \ 238 else \ 239 echo "<$$type>" >> $(testobj)/$*.tmp; \ 240 fi; \ 241 echo '<![CDATA[' >> $(testobj)/$*.tmp; \ 242 cat $(testobj)/$*.tmp.2 >> $(testobj)/$*.tmp; \ 243 echo "]]></$$type>" >> $(testobj)/$*.tmp; \ 244 rm -f $(testobj)/$*.tmp.2; \ 245 echo "</testcase>" >> $(testobj)/$*.tmp; \ 246 if [ $$type != 'failure' ]; then \ 247 $(MAKE) V=$(V) Q=$(Q) COV=$(COV) JUNIT_OUTPUT=y "run-$*"; \ 248 fi 249 250 251TESTS_BUILD_XML_FILE := $(testobj)/junit-libpayload-tests-build.xml 252 253$(TESTS_BUILD_XML_FILE): clean-junit.xml-unit-tests $(addprefix try-,$(alltests)) 254 mkdir -p $(dir $@) 255 echo '<?xml version="1.0" encoding="utf-8"?><testsuite>' > $@ 256 for tst in $(alltests); do \ 257 cat $(testobj)/$$tst.tmp >> $@; \ 258 done 259 echo "</testsuite>" >> $@ 260 261junit.xml-unit-tests: $(TESTS_BUILD_XML_FILE) 262 263clean-junit.xml-unit-tests: 264 rm -f $(TESTS_BUILD_XML_FILE) 265 266 267# Build a code coverage report by collecting all the gcov files into a single 268# report. If COV is not set, this might be a user error, and they're trying 269# to generate a coverage report without first having built and run the code 270# with code coverage. absence of COV=1 will be corrected. 271 272.PHONY: coverage-report clean-coverage-report 273 274ifeq ($(COV),1) 275coverage-report: 276 lcov -o $(testobj)/tests.info -c -d $(testobj) --exclude '$(testsrc)/*' 277 genhtml -q -o $(coverage-dir) -t "coreboot unit tests" -s $(testobj)/tests.info 278 279clean-coverage-report: 280 rm -Rf $(coverage-dir) 281else 282coverage-report: 283 COV=1 V=$(V) $(MAKE) coverage-report 284 285clean-coverage-report: 286 COV=1 V=$(V) $(MAKE) clean-coverage-report 287endif 288 289unit-tests: build-unit-tests run-unit-tests 290 291build-unit-tests: $(test-bins) 292 293run-unit-tests: $(alltests) 294 if [ `find $(testobj) -name '*.failed' | wc -l` -gt 0 ]; then \ 295 echo "**********************"; \ 296 echo " TESTS FAILED"; \ 297 echo "**********************"; \ 298 exit 1; \ 299 else \ 300 echo "**********************"; \ 301 echo " ALL TESTS PASSED"; \ 302 echo "**********************"; \ 303 exit 0; \ 304 fi 305 306$(addprefix clean-,$(alltests)): clean-%: 307 rm -rf $(testobj)/$* 308 309clean-unit-tests: 310 rm -rf $(testobj) 311 312list-unit-tests: 313 @echo "unit-tests:" 314 for t in $(sort $(alltests)); do \ 315 echo " $$t"; \ 316 done 317 318help-unit-tests help:: 319 @echo '*** libpayload unit-tests targets ***' 320 @echo ' Use "COV=1 make [target]" to enable code coverage for unit tests' 321 @echo ' unit-tests - Run all unit-tests from tests/' 322 @echo ' clean-unit-tests - Remove unit-tests build artifacts' 323 @echo ' list-unit-tests - List all unit-tests' 324 @echo ' <unit-test> - Build and run single unit-test' 325 @echo ' clean-<unit-test> - Remove single unit-test build artifacts' 326 @echo ' coverage-report - Generate a code coverage report' 327 @echo ' clean-coverage-report - Remove the code coverage report' 328 @echo 329