1# ========================================== 2# Unity Project - A Test Framework for C 3# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams 4# [Released under MIT License. Please refer to license.txt for details] 5# ========================================== 6 7require 'yaml' 8require 'fileutils' 9require_relative '../auto/unity_test_summary' 10require_relative '../auto/generate_test_runner' 11require_relative '../auto/colour_reporter' 12 13module RakefileHelpers 14 C_EXTENSION = '.c'.freeze 15 def load_configuration(config_file) 16 return if $configured 17 18 $cfg_file = "targets/#{config_file}" unless config_file =~ /[\\|\/]/ 19 $cfg = YAML.load(File.read($cfg_file)) 20 $colour_output = false unless $cfg['colour'] 21 $configured = true if config_file != DEFAULT_CONFIG_FILE 22 end 23 24 def configure_clean 25 CLEAN.include('build/*.*') 26 end 27 28 def configure_toolchain(config_file = DEFAULT_CONFIG_FILE) 29 config_file += '.yml' unless config_file =~ /\.yml$/ 30 config_file = config_file unless config_file =~ /[\\|\/]/ 31 load_configuration(config_file) 32 configure_clean 33 end 34 35 def unit_test_files 36 path = 'tests/test*' + C_EXTENSION 37 path.tr!('\\', '/') 38 FileList.new(path) 39 end 40 41 def local_include_dirs 42 include_dirs = $cfg[:paths][:includes] || [] 43 include_dirs += $cfg[:paths][:source] || [] 44 include_dirs += $cfg[:paths][:test] || [] 45 include_dirs += $cfg[:paths][:support] || [] 46 include_dirs.delete_if { |dir| dir.is_a?(Array) } 47 include_dirs 48 end 49 50 def extract_headers(filename) 51 includes = [] 52 lines = File.readlines(filename) 53 lines.each do |line| 54 m = line.match(/^\s*#include\s+\"\s*(.+\.[hH])\s*\"/) 55 includes << m[1] unless m.nil? 56 end 57 includes 58 end 59 60 def find_source_file(header, paths) 61 paths.each do |dir| 62 src_file = dir + header.ext(C_EXTENSION) 63 return src_file if File.exist?(src_file) 64 end 65 nil 66 end 67 68 def tackit(strings) 69 result = if strings.is_a?(Array) 70 "\"#{strings.join}\"" 71 else 72 strings 73 end 74 result 75 end 76 77 def squash(prefix, items) 78 result = '' 79 items.each { |item| result += " #{prefix}#{tackit(item)}" } 80 result 81 end 82 83 def should(behave, &block) 84 if block 85 puts 'Should ' + behave 86 yield block 87 else 88 puts "UNIMPLEMENTED CASE: Should #{behave}" 89 end 90 end 91 92 def build_command_string(hash, values, defines = nil) 93 94 # Replace named and numbered slots 95 args = [] 96 hash[:arguments].each do |arg| 97 if arg.include? '$' 98 if arg.include? ': COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE' 99 pattern = arg.gsub(': COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE','') 100 [ File.join('..','src') ].each do |f| 101 args << pattern.gsub(/\$/,f) 102 end 103 104 elsif arg.include? ': COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR' 105 pattern = arg.gsub(': COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR','') 106 [ $extra_paths, 'src', File.join('tests'), File.join('testdata'), $cfg[:paths][:support] ].flatten.uniq.compact.each do |f| 107 args << pattern.gsub(/\$/,f) 108 end 109 110 elsif arg.include? ': COLLECTION_DEFINES_TEST_AND_VENDOR' 111 pattern = arg.gsub(': COLLECTION_DEFINES_TEST_AND_VENDOR','') 112 [ $cfg[:defines][:test], defines ].flatten.uniq.compact.each do |f| 113 args << pattern.gsub(/\$/,f) 114 end 115 116 elsif arg =~ /\$\{(\d+)\}/ 117 i = $1.to_i - 1 118 if (values[i].is_a?(Array)) 119 values[i].each {|v| args << arg.gsub(/\$\{\d+\}/, v)} 120 else 121 args << arg.gsub(/\$\{(\d)+\}/, values[i] || '') 122 end 123 124 else 125 args << arg 126 127 end 128 else 129 args << arg 130 end 131 end 132 133 # Build Command 134 return tackit(hash[:executable]) + squash('', args) 135 end 136 137 def compile(file, defines = []) 138 out_file = File.join('build', File.basename(file, C_EXTENSION)) + $cfg[:extension][:object] 139 cmd_str = build_command_string( $cfg[:tools][:test_compiler], [ file, out_file ], defines ) 140 execute(cmd_str) 141 out_file 142 end 143 144 def link_it(exe_name, obj_list) 145 exe_name = File.join('build', File.basename(exe_name)) 146 cmd_str = build_command_string( $cfg[:tools][:test_linker], [ obj_list, exe_name ] ) 147 execute(cmd_str) 148 end 149 150 def runtest(bin_name, ok_to_fail = false, extra_args = nil) 151 bin_name = File.join('build', File.basename(bin_name)) 152 extra_args = extra_args.nil? ? "" : " " + extra_args 153 if $cfg[:tools][:test_fixture] 154 cmd_str = build_command_string( $cfg[:tools][:test_fixture], [ bin_name, extra_args ] ) 155 else 156 cmd_str = bin_name + extra_args 157 end 158 execute(cmd_str, ok_to_fail) 159 end 160 161 def run_astyle(style_what) 162 report "Styling C Code..." 163 command = "AStyle " \ 164 "--style=allman --indent=spaces=4 --indent-switches --indent-preproc-define --indent-preproc-block " \ 165 "--pad-oper --pad-comma --unpad-paren --pad-header " \ 166 "--align-pointer=type --align-reference=name " \ 167 "--add-brackets --mode=c --suffix=none " \ 168 "#{style_what}" 169 execute(command, false) 170 report "Styling C:PASS" 171 end 172 173 def execute(command_string, ok_to_fail = false) 174 report command_string if $verbose 175 output = `#{command_string}`.chomp 176 report(output) if $verbose && !output.nil? && !output.empty? 177 raise "Command failed. (Returned #{$?.exitstatus})" if !$?.nil? && !$?.exitstatus.zero? && !ok_to_fail 178 output 179 end 180 181 def report_summary 182 summary = UnityTestSummary.new 183 summary.root = __dir__ 184 results_glob = File.join('build','*.test*') 185 results_glob.tr!('\\', '/') 186 results = Dir[results_glob] 187 summary.targets = results 188 report summary.run 189 end 190 191 def save_test_results(test_base, output) 192 test_results = File.join('build',test_base) 193 if output.match(/OK$/m).nil? 194 test_results += '.testfail' 195 else 196 report output unless $verbose # Verbose already prints this line, as does a failure 197 test_results += '.testpass' 198 end 199 File.open(test_results, 'w') { |f| f.print output } 200 end 201 202 def test_fixtures() 203 report "\nRunning Fixture Addon" 204 205 # Get a list of all source files needed 206 src_files = Dir[File.join('..','extras','fixture','src','*.c')] 207 src_files += Dir[File.join('..','extras','fixture','test','*.c')] 208 src_files += Dir[File.join('..','extras','fixture','test','main','*.c')] 209 src_files += Dir[File.join('..','extras','memory','src','*.c')] 210 src_files << File.join('..','src','unity.c') 211 212 # Build object files 213 $extra_paths = [File.join('..','extras','fixture','src'), File.join('..','extras','memory','src')] 214 obj_list = src_files.map { |f| compile(f, ['UNITY_SKIP_DEFAULT_RUNNER', 'UNITY_FIXTURE_NO_EXTRAS']) } 215 216 # Link the test executable 217 test_base = File.basename('framework_test', C_EXTENSION) 218 link_it(test_base, obj_list) 219 220 # Run and collect output 221 output = runtest(test_base + " -v -r") 222 save_test_results(test_base, output) 223 end 224 225 def test_memory() 226 { 'w_malloc' => [], 227 'wo_malloc' => ['UNITY_EXCLUDE_STDLIB_MALLOC'] 228 }.each_pair do |name, defs| 229 report "\nRunning Memory Addon #{name}" 230 231 # Get a list of all source files needed 232 src_files = Dir[File.join('..','extras','memory','src','*.c')] 233 src_files += Dir[File.join('..','extras','memory','test','*.c')] 234 src_files += Dir[File.join('..','extras','memory','test','main','*.c')] 235 src_files << File.join('..','src','unity.c') 236 237 # Build object files 238 $extra_paths = [File.join('..','extras','memory','src')] 239 obj_list = src_files.map { |f| compile(f, defs) } 240 241 # Link the test executable 242 test_base = File.basename("memory_test_#{name}", C_EXTENSION) 243 link_it(test_base, obj_list) 244 245 # Run and collect output 246 output = runtest(test_base) 247 save_test_results(test_base, output) 248 end 249 end 250 251 def run_tests(test_files) 252 report "\nRunning Unity system tests" 253 254 include_dirs = local_include_dirs 255 256 # Build and execute each unit test 257 test_files.each do |test| 258 259 # Drop Out if we're skipping this type of test 260 if $cfg[:skip_tests] 261 if $cfg[:skip_tests].include?(:parameterized) && test.match(/parameterized/) 262 report("Skipping Parameterized Tests for this Target:IGNORE") 263 next 264 end 265 end 266 267 report "\nRunning Tests in #{test}" 268 obj_list = [] 269 test_defines = [] 270 271 # Detect dependencies and build required modules 272 extract_headers(test).each do |header| 273 # Compile corresponding source file if it exists 274 src_file = find_source_file(header, include_dirs) 275 276 obj_list << compile(src_file, test_defines) unless src_file.nil? 277 end 278 279 # Build the test runner (generate if configured to do so) 280 test_base = File.basename(test, C_EXTENSION) 281 runner_name = test_base + '_Runner.c' 282 runner_path = File.join('build',runner_name) 283 284 options = $cfg[:unity] 285 options[:use_param_tests] = test =~ /parameterized/ ? true : false 286 UnityTestRunnerGenerator.new(options).run(test, runner_path) 287 obj_list << compile(runner_path, test_defines) 288 289 # Build the test module 290 obj_list << compile(test, test_defines) 291 292 # Link the test executable 293 link_it(test_base, obj_list) 294 295 # Execute unit test and generate results file 296 output = runtest(test_base) 297 save_test_results(test_base, output) 298 end 299 end 300 301 def run_make_tests() 302 [ "make -s", # test with all defaults 303 #"make -s DEBUG=-m32", # test 32-bit architecture with 64-bit support 304 #"make -s DEBUG=-m32 UNITY_SUPPORT_64=", # test 32-bit build without 64-bit types 305 "make -s UNITY_INCLUDE_DOUBLE= ", # test without double 306 "cd #{File.join("..","extras","fixture",'test')} && make -s default noStdlibMalloc", 307 "cd #{File.join("..","extras","fixture",'test')} && make -s C89", 308 "cd #{File.join("..","extras","memory",'test')} && make -s default noStdlibMalloc", 309 "cd #{File.join("..","extras","memory",'test')} && make -s C89", 310 ].each do |cmd| 311 report "Testing '#{cmd}'" 312 execute(cmd, false) 313 end 314 end 315end 316