diff --git a/auto/generate_test_runner.rb b/auto/generate_test_runner.rb new file mode 100644 index 0000000..68afb09 --- /dev/null +++ b/auto/generate_test_runner.rb @@ -0,0 +1,176 @@ + +class UnityTestRunnerGenerator + + def run(input_file, output_file, additional_includes=[], tab=' ') + @tab = tab + tests = [] + includes = [] + used_mocks = [] + + module_name = File.basename(input_file) + + File.open(input_file, 'r') do |input| + tests = find_tests(input) + includes = find_includes(input) + used_mocks = find_mocks(includes) + end + + File.open(output_file, 'w') do |output| + create_header(output, used_mocks, additional_includes) + create_externs(output, tests, used_mocks) + create_mock_management(output, used_mocks) + create_runtest(output, used_mocks) + create_main(output, module_name, tests) + end + + all_files_used = [input_file, output_file] + all_files_used += includes.map {|filename| filename + '.c'} unless includes.empty? + all_files_used += additional_includes unless additional_includes.empty? + return all_files_used.uniq + end + + def find_tests(input_file) + input_file.rewind + tests = [] + source = input_file.read() + source = source.gsub(/\/\/.*$/, '') #remove line comments + source = source.gsub(/\/\*.*?\*\//m, '') #remove block comments + lines = source.split(/(^\s*\#.*$) # Treat preprocessor directives as a logical line + | (;|\{|\}) /x) # Match ;, {, and } as end of lines + lines.each do |line| + if line =~ /^\s*void\s+test(.*?)\s*\(\s*void\s*\)/ + tests << "test" + $1 + end + end + return tests + end + + def find_includes(input_file) + input_file.rewind + includes = [] + input_file.readlines.each do |line| + scan_results = line.scan(/^#include\s+\"\s*(.+)\.h\s*\"/) + includes << scan_results[0][0] if (scan_results.size > 0) + end + return includes + end + + def find_mocks(includes) + mock_headers = [] + includes.each do |include_file| + mock_headers << include_file if include_file.include? "Mock" + end + return mock_headers + end + + def create_header(output, mocks, additional_includes=[]) + output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */') + output.puts('#include "unity.h"') + additional_includes.each do |includes| + output.puts("#include \"#{includes}.h\"") + end + mocks.each do |mock| + output.puts("#include \"#{mock}.h\"") + end + output.puts('#include ') + output.puts('#include ') + output.puts('') + output.puts('jmp_buf AbortFrame;') + output.puts('') + output.puts('char MessageBuffer[50];') + end + + + def create_externs(output, tests, mocks) + output.puts('') + + output.puts("extern void setUp(void);") + output.puts("extern void tearDown(void);") + + output.puts('') + + tests.each do |test| + output.puts("extern void #{test}(void);") + end + + output.puts('') + end + + + def create_mock_management(output, mocks) + unless (mocks.empty?) + output.puts("static void CMock_Init(void)") + output.puts("{") + mocks.each do |mock| + output.puts("#{@tab}#{mock}_Init();") + end + output.puts("}\n") + + output.puts("static void CMock_Verify(void)") + output.puts("{") + mocks.each do |mock| + output.puts("#{@tab}#{mock}_Verify();") + end + output.puts("}\n") + + output.puts("static void CMock_Destroy(void)") + output.puts("{") + mocks.each do |mock| + output.puts("#{@tab}#{mock}_Destroy();") + end + output.puts("}\n") + end + end + + + def create_runtest(output, used_mocks) + output.puts("static void runTest(UnityTestFunction test)") + output.puts("{") + output.puts("#{@tab}if (TEST_PROTECT())") + output.puts("#{@tab}{") + output.puts("#{@tab}#{@tab}CMock_Init();") unless (used_mocks.empty?) + output.puts("#{@tab}#{@tab}setUp();") + output.puts("#{@tab}#{@tab}test();") + output.puts("#{@tab}}") + output.puts("#{@tab}TEST_WRAP(CMock_Verify());") unless (used_mocks.empty?) + output.puts("#{@tab}CMock_Destroy();") unless (used_mocks.empty?) + output.puts("#{@tab}tearDown();") + output.puts("}") + end + + + def create_main(output, module_name, tests) + output.puts() + output.puts() + output.puts("void main(void)") + output.puts("{") + output.puts("#{@tab}Unity.TestFile = \"#{module_name}\";") + output.puts("#{@tab}UnityBegin();") + output.puts() + + output.puts("#{@tab}// RUN_TEST calls runTest") + tests.each do |test| + output.puts("#{@tab}RUN_TEST(#{test});") + end + + output.puts() + output.puts("#{@tab}UnityEnd();") + output.puts("}") + end + +end + + +if ($0 == __FILE__) + usage = "usage: ruby #{__FILE__} input_test_file output_test_runner" + + if !ARGV[0] + puts usage + exit 1 + end + + ARGV[1] = ARGV[0].gsub(".c","_sRunner.c") if (!ARGV[1]) + + UnityTestRunnerGenerator.new + UnityTestRunnerGenerator.run(ARGV[0], ARGV[1]) +end diff --git a/docs/Unity Summary.odt b/docs/Unity Summary.odt index fb5745e..73791e7 100644 Binary files a/docs/Unity Summary.odt and b/docs/Unity Summary.odt differ diff --git a/docs/Unity Summary.pdf b/docs/Unity Summary.pdf index 2ddf129..1b7ed79 100644 Binary files a/docs/Unity Summary.pdf and b/docs/Unity Summary.pdf differ diff --git a/examples/rakefile b/examples/rakefile new file mode 100644 index 0000000..e3e1ded --- /dev/null +++ b/examples/rakefile @@ -0,0 +1,60 @@ +$here = File.expand_path( File.dirname( __FILE__ ) ) + +require 'rake/clean' +require 'rake/loaders/makefile' +require 'fileutils' +require 'set' +require '../auto/unity_test_summary' +require '../auto/generate_test_runner' +require 'rakefile_helper' + +include RakefileHelpers + +CLEAN.include('build/*') + +desc "Build and run all tests, then output results (you can just type \"rake\" to get this." +task :default => [:clobber, :all, :summary] + +task :summary do + flush_output + summary = UnityTestSummary.new + summary.set_root_path($here) + summary.set_targets(Dir[BUILD_PATH+'/*.test*']) + summary.run +end + +task :all do + puts "Starting Test Suite" + runner_generator = UnityTestRunnerGenerator.new + test_sets = {} + + #compile unity files + Dir[UNITY_PATH+'/*.c'].each do |file| + compile(file, BUILD_PATH+'/'+File.basename(file).gsub('.c', OBJ_EXTENSION)) + end + + #compile source files + Dir[SOURCE_PATH+'/*.c'].each do |file| + compile(file, BUILD_PATH+'/'+File.basename(file).gsub('.c', OBJ_EXTENSION)) + end + + #compile test files + Dir[UNIT_TEST_PATH+'/*.c'].each do |file| + compile(file, BUILD_PATH+'/'+File.basename(file).gsub('.c', OBJ_EXTENSION)) + end + + #compile runner files + Dir[UNIT_TEST_PATH+'/*.c'].each do |file| + run_file = BUILD_PATH+'/'+File.basename(file).gsub('.c','_Runner.c') + test_set = runner_generator.run(file, run_file) + compile(run_file, run_file.gsub('.c', OBJ_EXTENSION)) + test_sets[run_file.gsub('_Runner.c', BIN_EXTENSION)] = test_set.map {|req_file| BUILD_PATH + '/' + File.basename(req_file).gsub(/\.[c|h]/, OBJ_EXTENSION)} + end + + #link and run each test + test_sets.each_pair do |exe_file, obj_files| + link(obj_files, exe_file) + write_result_file(exe_file, run_test(exe_file)) + end +end + diff --git a/examples/rakefile_helper.rb b/examples/rakefile_helper.rb new file mode 100644 index 0000000..cca6582 --- /dev/null +++ b/examples/rakefile_helper.rb @@ -0,0 +1,103 @@ +HERE = File.expand_path( File.dirname( __FILE__ ) ).gsub(/\//, '\\') + +module RakefileConstants + + PROGRAM_FILES_PATH = ENV['ProgramFiles'] + begin + Dir.new PROGRAM_FILES_PATH + '\IAR Systems\Embedded Workbench 4.0\arm' + IAR_ROOT = PROGRAM_FILES_PATH + '\IAR Systems\Embedded Workbench 4.0' + rescue + Dir.new PROGRAM_FILES_PATH + '\IAR Systems\Embedded Workbench 4.0 Kickstart\arm' + IAR_ROOT = PROGRAM_FILES_PATH + '\IAR Systems\Embedded Workbench 4.0 Kickstart' + end + + C_EXTENSION = '.c' + OBJ_EXTENSION = '.r79' + BIN_EXTENSION = '.d79' + + UNIT_TEST_PATH = 'test' + UNITY_PATH = '../src' + SOURCE_PATH = 'src' + BUILD_PATH = 'build' + IAR_PATH = IAR_ROOT + '\common' + IAR_BIN = IAR_PATH + '\bin' + IAR_INCLUDE = IAR_PATH + '\inc' + IAR_CORE_PATH = IAR_ROOT + '\arm' + IAR_CORE_BIN = IAR_CORE_PATH + '\bin' + IAR_CORE_CONFIG = IAR_CORE_PATH + '\config' + IAR_CORE_INCLUDE = IAR_CORE_PATH + '\inc' + IAR_CORE_INCLUDE_DLIB = IAR_CORE_INCLUDE + '\lib' + IAR_CORE_LIB = IAR_CORE_PATH + '\lib' + IAR_CORE_DLIB = IAR_CORE_LIB + '\dl5tpannl8n.r79' + IAR_CORE_DLIB_CONFIG = IAR_CORE_LIB + '\dl5tpannl8n.h' + IAR_PROCESSOR_SPECIFIC_PATH = HERE + '\proc' + SIMULATOR_PROCESSOR = IAR_CORE_BIN + '\armproc.dll' + SIMULATOR_DRIVER = IAR_CORE_BIN + '\armsim.dll' + SIMULATOR_PLUGIN = IAR_CORE_BIN + '\armbat.dll' + SIMULATOR_BACKEND_DDF = IAR_CORE_CONFIG + '\ioat91sam9261.ddf' + PROCESSOR_TYPE = "ARM926EJ-S" + LINKER_CONFIG = IAR_CORE_CONFIG + '\lnkarm.xcl' + + UNITY_SRC = UNITY_PATH + '\unity.c' + UNITY_HDR = UNITY_PATH + '\unity.h' + UNITY_OBJ = BUILD_PATH + '\unity' + OBJ_EXTENSION + UNITY_TEST_OBJ = BUILD_PATH + '\testunity' + OBJ_EXTENSION + UNITY_TEST_RUNNER_OBJ = BUILD_PATH + '\testunity_Runner' + OBJ_EXTENSION + UNITY_TEST_EXEC = UNITY_TEST_OBJ.ext BIN_EXTENSION + TEST_RESULTS = UNITY_TEST_OBJ.ext '.testpass' + + COMPILER = IAR_CORE_BIN + '\iccarm.exe' + LINKER = IAR_BIN + '\xlink.exe' + SIMULATOR = IAR_BIN + '\CSpyBat.exe' + +end + +module RakefileHelpers + include RakefileConstants + + def flush_output + $stderr.flush + $stdout.flush + end + + def report message + puts message + flush_output + end + + def compile src, obj + execute "#{COMPILER} --dlib_config \"#{IAR_CORE_DLIB_CONFIG}\" -z3 --no_cse --no_unroll --no_inline --no_code_motion --no_tbaa --no_clustering --no_scheduling --debug --cpu_mode arm --endian little --cpu #{PROCESSOR_TYPE} --stack_align 4 -e --fpu None --diag_suppress Pa050 --diag_suppress Pe111 -I\"#{IAR_CORE_INCLUDE}\" -I\"#{UNITY_PATH}\" -Isrc -Itest #{src} -o#{obj}" + end + + def link prerequisites, executable + execute "\"#{LINKER}\" -rt \"#{IAR_CORE_DLIB}\" -B -s __program_start -I\"#{IAR_CORE_CONFIG}\" -I\"#{IAR_CORE_LIB}\" -f \"#{LINKER_CONFIG}\" #{prerequisites.join(' ')} -o #{executable}" + end + + def run_test executable + execute "\"#{SIMULATOR}\" --silent \"#{SIMULATOR_PROCESSOR}\" \"#{SIMULATOR_DRIVER}\" #{executable} --plugin \"#{SIMULATOR_PLUGIN}\" --backend -B --cpu #{PROCESSOR_TYPE} -p \"#{SIMULATOR_BACKEND_DDF}\" -d sim" + end + + def write_result_file filename, results + if (results.include?("OK\n")) + output_file = filename.gsub(BIN_EXTENSION, '.testpass') + else + output_file = filename.gsub(BIN_EXTENSION, '.testfail') + end + File.open(output_file, 'w') do |f| + f.print results + end + end + +private ##################### + + def execute command_string + report command_string + output = `#{command_string}` + report output + if $?.exitstatus != 0 + raise "Command failed. (Returned #{$?.exitstatus})" + end + output + end + +end diff --git a/examples/src/ProductionCode.c b/examples/src/ProductionCode.c new file mode 100644 index 0000000..1ec9433 --- /dev/null +++ b/examples/src/ProductionCode.c @@ -0,0 +1,24 @@ + +#include "ProductionCode.h" + +int Counter = 0; +int NumbersToFind[9] = { 0, 34, 55, 66, 32, 11, 1, 77, 888 }; //some obnoxious array to search that is 1-based indexing instead of 0. + +// This function is supposed to search through NumbersToFind and find a particular number. +// If it finds it, the index is returned. Otherwise 0 is returned which sorta makes sense since +// NumbersToFind is indexed from 1. Unfortunately it's broken +// (and should therefore be caught by our tests) +int FindFunction_WhichIsBroken(int NumberToFind) +{ + int i = 0; + while (i <= 8) //Notice I should have been in braces + i++; + if (NumbersToFind[i] == NumberToFind) //Yikes! I'm getting run after the loop finishes instead of during it! + return i; + return 0; +} + +int FunctionWhichReturnsLocalVariable(void) +{ + return Counter; +} diff --git a/examples/src/ProductionCode.h b/examples/src/ProductionCode.h new file mode 100644 index 0000000..ca55d80 --- /dev/null +++ b/examples/src/ProductionCode.h @@ -0,0 +1,3 @@ + +int FindFunction_WhichIsBroken(int NumberToFind); +int FunctionWhichReturnsLocalVariable(void); diff --git a/examples/src/ProductionCode2.c b/examples/src/ProductionCode2.c new file mode 100644 index 0000000..543f4e4 --- /dev/null +++ b/examples/src/ProductionCode2.c @@ -0,0 +1,8 @@ + +#include "ProductionCode2.h" + +char* ThisFunctionHasNotBeenTested(int Poor, char* LittleFunction) +{ + //Since There Are No Tests Yet, This Function Could Be Empty For All We Know. + // Which isn't terribly useful... but at least we put in a TEST_IGNORE so we won't forget +} diff --git a/examples/src/ProductionCode2.h b/examples/src/ProductionCode2.h new file mode 100644 index 0000000..3047e68 --- /dev/null +++ b/examples/src/ProductionCode2.h @@ -0,0 +1,2 @@ + +char* ThisFunctionHasNotBeenTested(int Poor, char* LittleFunction); diff --git a/examples/test/TestProductionCode.c b/examples/test/TestProductionCode.c new file mode 100644 index 0000000..027d7cf --- /dev/null +++ b/examples/test/TestProductionCode.c @@ -0,0 +1,62 @@ + +#include "ProductionCode.h" +#include "unity.h" + +//sometimes you may want to get at local data in a module. +//for example: If you plan to pass by reference, this could be useful +//however, it should often be avoided +extern int Counter; + +void setUp(void) +{ + //This is run before EACH TEST + Counter = 0x5a5a; +} + +void tearDown(void) +{ +} + +void test_FindFunction_WhichIsBroken_ShouldReturnZeroIfItemIsNotInList_WhichWorksEvenInOurBrokenCode(void) +{ + //All of these should pass + TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(78)); + TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(1)); + TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(33)); + TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(999)); + TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(-1)); +} + +void test_FindFunction_WhichIsBroken_ShouldReturnTheIndexForItemsInList_WhichWillFailBecauseOurFunctionUnderTestIsBroken(void) +{ + // You should see this line fail in your test summary + TEST_ASSERT_EQUAL(1, FindFunction_WhichIsBroken(34)); + + // Notice the rest of these didn't get a chance to run because the line above failed. + // Unit tests abort each test function on the first sign of trouble. + // Then NEXT test function runs as normal. + TEST_ASSERT_EQUAL(8, FindFunction_WhichIsBroken(8888)); +} + +void test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValue(void) +{ + //This should be true because setUp set this up for us before this test + TEST_ASSERT_EQUAL_HEX(0x5a5a, FunctionWhichReturnsLocalVariable()); + + //This should be true because we can still change our answer + Counter = 0x1234; + TEST_ASSERT_EQUAL_HEX(0x1234, FunctionWhichReturnsLocalVariable()); +} + +void test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValueAgain(void) +{ + //This should be true again because setup was rerun before this test (and after we changed it to 0x1234) + TEST_ASSERT_EQUAL_HEX(0x5a5a, FunctionWhichReturnsLocalVariable()); +} + +void test_FunctionWhichReturnsLocalVariable_ShouldReturnCurrentCounter_ButFailsBecauseThisTestIsActuallyFlawed(void) +{ + //Sometimes you get the test wrong. When that happens, you get a failure too... and a quick look should tell + // you what actually happened...which in this case was a failure to setup the initial condition. + TEST_ASSERT_EQUAL_HEX(0x1234, FunctionWhichReturnsLocalVariable()); +} diff --git a/examples/test/TestProductionCode2.c b/examples/test/TestProductionCode2.c new file mode 100644 index 0000000..8192d32 --- /dev/null +++ b/examples/test/TestProductionCode2.c @@ -0,0 +1,26 @@ + +#include "ProductionCode2.h" +#include "unity.h" + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void test_IgnoredTest(void) +{ + TEST_IGNORE_MESSAGE("This Test Was Ignored On Purpose"); +} + +void test_AnotherIgnoredTest(void) +{ + TEST_IGNORE_MESSAGE("These Can Be Useful For Leaving Yourself Notes On What You Need To Do Yet"); +} + +void test_ThisFunctionHasNotBeenTested_NeedsToBeImplemented(void) +{ + TEST_IGNORE(); //Like This +} diff --git a/makefile b/makefile index af59374..47ae2d5 100644 --- a/makefile +++ b/makefile @@ -12,9 +12,9 @@ INC_DIRS=-Isrc SYMBOLS=-DTEST ifeq ($(OS),Windows_NT) - CLEANUP = del /F /Q bin\* && del /F /Q $(TARGET) + CLEANUP = del /F /Q build\* && del /F /Q $(TARGET) else - CLEANUP = rm -f bin/*.o ; rm -f $(TARGET) + CLEANUP = rm -f build/*.o ; rm -f $(TARGET) endif all: clean default diff --git a/rakefile b/rakefile index 6e43ec6..8450c97 100644 --- a/rakefile +++ b/rakefile @@ -9,7 +9,7 @@ require 'rakefile_helper' include RakefileHelpers -CLEAN.include('bin/*') +CLEAN.include('build/*') desc "Build Unity and run tests." task :default => [:clobber, :all] @@ -25,7 +25,7 @@ task :summary do flush_output summary = UnityTestSummary.new summary.set_root_path($here) - summary.set_targets(Dir["bin/*.test*"]) + summary.set_targets(Dir["build/*.test*"]) summary.run end @@ -47,7 +47,7 @@ end rule /.*\.test/ => [BIN_EXTENSION] do |file| bin = file.name.ext BIN_EXTENSION - test_results = 'bin\sim.txt' + test_results = 'build\sim.txt' fail_file = file.name.ext 'testfail' if File.exist?(fail_file) rm_f fail_file diff --git a/rakefile_helper.rb b/rakefile_helper.rb index fd3cb33..7c628cc 100644 --- a/rakefile_helper.rb +++ b/rakefile_helper.rb @@ -17,7 +17,7 @@ module RakefileConstants UNIT_TEST_PATH = 'test' SOURCE_PATH = 'src' - BIN_PATH = 'bin' + BIN_PATH = 'build' IAR_PATH = IAR_ROOT + '\common' IAR_BIN = IAR_PATH + '\bin' IAR_INCLUDE = IAR_PATH + '\inc'