require 'fileutils' ABS_ROOT = FileUtils.pwd CMOCK_DIR = File.expand_path(ENV.fetch('CMOCK_DIR', File.join(ABS_ROOT, '..', '..'))) require "#{CMOCK_DIR}/lib/cmock" UNITY_DIR = File.join(CMOCK_DIR, 'vendor', 'unity') require "#{UNITY_DIR}/auto/generate_test_runner" SRC_DIR = ENV.fetch('SRC_DIR', './src') TEST_DIR = ENV.fetch('TEST_DIR', './test') UNITY_SRC = File.join(UNITY_DIR, 'src') CMOCK_SRC = File.join(CMOCK_DIR, 'src') BUILD_DIR = ENV.fetch('BUILD_DIR', './build') TEST_BUILD_DIR = ENV.fetch('TEST_BUILD_DIR', File.join(BUILD_DIR, 'test')) OBJ_DIR = File.join(TEST_BUILD_DIR, 'obj') UNITY_OBJ = File.join(OBJ_DIR, 'unity.o') CMOCK_OBJ = File.join(OBJ_DIR, 'cmock.o') RUNNERS_DIR = File.join(TEST_BUILD_DIR, 'runners') MOCKS_DIR = File.join(TEST_BUILD_DIR, 'mocks') TEST_BIN_DIR = TEST_BUILD_DIR MOCK_PREFIX = ENV.fetch('TEST_MOCK_PREFIX', 'mock_') MOCK_SUFFIX = ENV.fetch('TEST_MOCK_SUFFIX', '') TEST_MAKEFILE = ENV.fetch('TEST_MAKEFILE', File.join(TEST_BUILD_DIR, 'MakefileTestSupport')) MOCK_MATCHER = /#{MOCK_PREFIX}[A-Za-z_][A-Za-z0-9_\-\.]+#{MOCK_SUFFIX}/ [TEST_BUILD_DIR, OBJ_DIR, RUNNERS_DIR, MOCKS_DIR, TEST_BIN_DIR].each do |dir| FileUtils.mkdir_p dir end all_headers_to_mock = [] suppress_error = !ARGV.nil? && !ARGV.empty? && (ARGV[0].upcase == "--SILENT") File.open(TEST_MAKEFILE, "w") do |mkfile| # Define make variables mkfile.puts "CC ?= gcc" mkfile.puts "BUILD_DIR = #{BUILD_DIR}" mkfile.puts "SRC_DIR = #{SRC_DIR}" mkfile.puts "TEST_DIR = #{TEST_DIR}" mkfile.puts "TEST_CFLAGS ?= -DTEST" mkfile.puts "CMOCK_DIR ?= #{CMOCK_DIR}" mkfile.puts "UNITY_DIR ?= #{UNITY_DIR}" mkfile.puts "TEST_BUILD_DIR ?= ${BUILD_DIR}/test" mkfile.puts "TEST_MAKEFILE = ${TEST_BUILD_DIR}/MakefileTestSupport" mkfile.puts "OBJ ?= ${BUILD_DIR}/obj" mkfile.puts "OBJ_DIR = ${OBJ}" mkfile.puts "" # Build Unity mkfile.puts "#{UNITY_OBJ}: #{UNITY_SRC}/unity.c" mkfile.puts "\t${CC} -o $@ -c $< -I #{UNITY_SRC}" mkfile.puts "" # Build CMock mkfile.puts "#{CMOCK_OBJ}: #{CMOCK_SRC}/cmock.c" mkfile.puts "\t${CC} -o $@ -c $< -I #{UNITY_SRC} -I #{CMOCK_SRC}" mkfile.puts "" test_sources = Dir["#{TEST_DIR}/**/test_*.c"] test_targets = [] generator = UnityTestRunnerGenerator.new # headers that begin with prefix or end with suffix are not included all_headers = Dir["#{SRC_DIR}/**/*.h"] def reject_mock_files(file) extn = File.extname file filename = File.basename file, extn if MOCK_SUFFIX.empty? return filename.start_with? MOCK_PREFIX end return (filename.start_with? MOCK_PREFIX or filename.end_with? MOCK_SUFFIX) end all_headers = all_headers.reject { |f| reject_mock_files(f) } makefile_targets = [] test_sources.each do |test| module_name = File.basename(test, '.c') src_module_name = module_name.sub(/^test_/, '') test_obj = File.join(OBJ_DIR, "#{module_name}.o") runner_source = File.join(RUNNERS_DIR, "runner_#{module_name}.c") runner_obj = File.join(OBJ_DIR, "runner_#{module_name}.o") test_bin = File.join(TEST_BIN_DIR, module_name) test_results = File.join(TEST_BIN_DIR, module_name + '.testresult') cfg = { src: test, includes: generator.find_includes(File.readlines(test).join('')) } # Build main project modules, with TEST defined module_src = File.join(SRC_DIR, "#{src_module_name}.c") module_obj = File.join(OBJ_DIR, "#{src_module_name}.o") if not makefile_targets.include? module_obj makefile_targets.push(module_obj) mkfile.puts "#{module_obj}: #{module_src}" mkfile.puts "\t${CC} -o $@ -c $< ${TEST_CFLAGS} -I ${SRC_DIR} ${INCLUDE_PATH}" mkfile.puts "" end # process link-only files linkonly = cfg[:includes][:linkonly] linkonly_objs = [] linkonly.each do |linkonlyfile| linkonlybase = File.basename(linkonlyfile) linkonlymodule_src = File.join(SRC_DIR, "#{linkonlyfile}.c") linkonlymodule_obj = File.join(OBJ_DIR, "#{linkonlybase}.o") linkonly_objs.push(linkonlymodule_obj) #only create the target if we didn't already if not makefile_targets.include? linkonlymodule_obj makefile_targets.push(linkonlymodule_obj) mkfile.puts "#{linkonlymodule_obj}: #{linkonlymodule_src}" mkfile.puts "\t${CC} -o $@ -c $< ${TEST_CFLAGS} -I ${SRC_DIR} ${INCLUDE_PATH}" mkfile.puts "" end end # Create runners mkfile.puts "#{runner_source}: #{test}" mkfile.puts "\t@UNITY_DIR=${UNITY_DIR} ruby ${CMOCK_DIR}/scripts/create_runner.rb #{test} #{runner_source}" mkfile.puts "" # Build runner mkfile.puts "#{runner_obj}: #{runner_source}" mkfile.puts "\t${CC} -o $@ -c $< ${TEST_CFLAGS} -I #{SRC_DIR} -I #{MOCKS_DIR} -I #{UNITY_SRC} -I #{CMOCK_SRC} ${INCLUDE_PATH}" mkfile.puts "" # Collect mocks to generate system_mocks = cfg[:includes][:system].select{|name| name =~ MOCK_MATCHER} raise "Mocking of system headers is not yet supported!" if !system_mocks.empty? local_mocks = cfg[:includes][:local].select{|name| name =~ MOCK_MATCHER} module_names_to_mock = local_mocks.map{|name| "#{name.sub(/#{MOCK_PREFIX}/,'')}.h"} headers_to_mock = [] module_names_to_mock.each do |name| header_to_mock = nil all_headers.each do |header| if (header =~ /[\/\\]?#{name}$/) header_to_mock = header break end end raise "Module header '#{name}' not found to mock!" unless header_to_mock headers_to_mock << header_to_mock end all_headers_to_mock += headers_to_mock mock_objs = headers_to_mock.map do |hdr| mock_name = MOCK_PREFIX + File.basename(hdr, '.h') File.join(MOCKS_DIR, mock_name + '.o') end all_headers_to_mock.uniq! # Build test suite mkfile.puts "#{test_obj}: #{test} #{module_obj} #{mock_objs.join(' ')}" mkfile.puts "\t${CC} -o $@ -c $< ${TEST_CFLAGS} -I #{SRC_DIR} -I #{UNITY_SRC} -I #{CMOCK_SRC} -I #{MOCKS_DIR} ${INCLUDE_PATH}" mkfile.puts "" # Build test suite executable test_objs = "#{test_obj} #{runner_obj} #{module_obj} #{mock_objs.join(' ')} #{linkonly_objs.join(' ')} #{UNITY_OBJ} #{CMOCK_OBJ}" mkfile.puts "#{test_bin}: #{test_objs}" mkfile.puts "\t${CC} -o $@ ${LDFLAGS} #{test_objs}" mkfile.puts "" # Run test suite and generate report mkfile.puts "#{test_results}: #{test_bin}" mkfile.puts "\t-#{test_bin} > #{test_results} 2>&1" mkfile.puts "" test_targets << test_bin end # Generate and build mocks all_headers_to_mock.each do |hdr| mock_name = MOCK_PREFIX + File.basename(hdr, '.h') mock_header = File.join(MOCKS_DIR, mock_name + '.h') mock_src = File.join(MOCKS_DIR, mock_name + '.c') mock_obj = File.join(MOCKS_DIR, mock_name + '.o') mkfile.puts "#{mock_src}: #{hdr}" mkfile.puts "\t@CMOCK_DIR=${CMOCK_DIR} ruby ${CMOCK_DIR}/scripts/create_mock.rb #{hdr}" mkfile.puts "" mkfile.puts "#{mock_obj}: #{mock_src} #{mock_header}" mkfile.puts "\t${CC} -o $@ -c $< ${TEST_CFLAGS} -I #{MOCKS_DIR} -I #{SRC_DIR} -I #{UNITY_SRC} -I #{CMOCK_SRC} ${INCLUDE_PATH}" mkfile.puts "" end # Create test summary task mkfile.puts "test_summary:" mkfile.puts "\t@UNITY_DIR=${UNITY_DIR} ruby ${CMOCK_DIR}/scripts/test_summary.rb #{suppress_error ? '--silent' : ''}" mkfile.puts "" mkfile.puts ".PHONY: test_summary" mkfile.puts "" # Create target to run all tests mkfile.puts "test: #{test_targets.map{|t| t + '.testresult'}.join(' ')} test_summary" mkfile.puts "" end