From 42fba80136aa24710e6511b1856cbda7de505c26 Mon Sep 17 00:00:00 2001 From: greg-williams Date: Sun, 1 Jun 2008 18:09:28 +0000 Subject: [PATCH] git-svn-id: http://cmock.svn.sourceforge.net/svnroot/cmock/trunk@5 bf332499-1b4d-0410-844d-d2d48d5cc64c --- lib/cmock.rb | 19 +- lib/cmock_generator.rb | 545 +++++++++++++++++++++++++++++++++++++ lib/cmock_header_parser.rb | 144 ++++++++++ lib/cmock_new.rb | 15 + lib/cmock_setup.rb | 31 +++ 5 files changed, 739 insertions(+), 15 deletions(-) create mode 100644 lib/cmock_generator.rb create mode 100644 lib/cmock_header_parser.rb create mode 100644 lib/cmock_new.rb create mode 100644 lib/cmock_setup.rb diff --git a/lib/cmock.rb b/lib/cmock.rb index eb77c4c..f2dd435 100644 --- a/lib/cmock.rb +++ b/lib/cmock.rb @@ -1,15 +1,4 @@ - -class CMock - attr_accessor :mocks_path, :includes, :interface_parser - - def initialize(mocks_path='mocks', includes=[], interface_parser=nil) - @mocks_path = mocks_path - @includes = includes - @interface_parser = interface_parser - end - - def generate(module_header) - @interface_parser.extract_interface(module_header) - end - -end \ No newline at end of file +$here = File.dirname __FILE__ +require "#{$here}/cmock_setup" +require "#{$here}/cmock_generator" +require "#{$here}/c_file_parser" diff --git a/lib/cmock_generator.rb b/lib/cmock_generator.rb new file mode 100644 index 0000000..b8ccee2 --- /dev/null +++ b/lib/cmock_generator.rb @@ -0,0 +1,545 @@ +$here = File.dirname __FILE__ +require "#{$here}/c_file_parser" + +class CMockGenerator + + attr_accessor :module_name, :src_path, :mock_path, :tab, :includes + + require 'ftools' + + def initialize(module_name=nil, src_path='src', mock_path='mocks', includes=[], use_cexception=true, allow_ignore_mock=false, tab=' ') + raise "Must at least specify a code module" if module_name.nil? + @module_name = module_name + @src_path = src_path + @mock_path = mock_path + @tab = tab + @throw_type = 'int' + @call_count_type = 'unsigned short' + @ignore_bool = 'unsigned char' + @includes = includes + @use_cexception = use_cexception + @allow_ignore_mock = allow_ignore_mock + end + + def create_mock + update_vars + create_mock_header_file + create_mock_source_file + end + + def update_vars + @parser = CFileParser.new(File.read("#{@src_path}/#{@module_name}.h")) + @mock_name = "Mock" + @module_name + @mock_header_name_dest = @mock_name + ".h" + @mock_imp_name_dest = @mock_name + ".c" + @mock_header_temp = @mock_name + ".h.new" + @mock_imp_temp = @mock_name + ".c.new" + @mock_header_file_path = "#{@mock_path}/#{@mock_header_name_dest}" + @mock_imp_file_path = "#{@mock_path}/#{@mock_imp_name_dest}" + end + + def update_file(dest, src) + File.delete(dest) if (File.exist?(dest)) + File.copy(src, dest) + File.delete(src) + end + + def create_mock_header_file + File.open @mock_header_temp, 'w' do |header| + @header = header + create_mock_header_header(@mock_header_name_dest.gsub(/\.h/, "_h").upcase) + create_mock_header_externs + @parser.nondefine_functions.each do |function| + create_mock_header_function_declaration(function) + end + create_mock_header_footer + end + update_file @mock_header_file_path, @mock_header_temp + end + + def create_mock_source_file + File.open @mock_imp_temp, 'w' do |implementation| + @source = implementation + create_source_header_section + create_instance_structure + create_extern_declarations + create_mock_verify_function + create_mock_init_function + create_mock_destroy_function + @parser.nondefine_functions.each do |function| + create_mock_implementation(function) + create_mock_function_expectation(function) + create_mock_function_expectation_with_throw(function) if (@use_cexception) + create_mock_function_ignore(function) if (@allow_ignore_mock) + end + end + update_file @mock_imp_file_path, @mock_imp_temp + end + + private + + def create_mock_header_header(define_name) + @header << "/* AUTOGENERATED FILE. DO NOT EDIT. */\n" + @header << "#ifndef _#{define_name}\n" + @header << "#define _#{define_name}\n\n" + @header << "#include \"#{@mock_header_name_dest.gsub(/^Mock/, "")}\"\n\n" + @header << "void #{@mock_name}_Init(void);\n" + @header << "void #{@mock_name}_Destroy(void);\n" + @header << "void #{@mock_name}_Verify(void);\n\n" + end + + def create_mock_header_externs + @parser.externs.each do |extern| + @header << extern << ";\n" + end + @header << "\n" unless @parser.externs.empty? + end + + def create_mock_header_function_declaration(function) + decl = parse_declaration(function) + + if decl[:args_no_var_args] == "void" + if decl[:return] == "void" + @header << "void #{decl[:function]}_Expect(void);\n" + else + @header << "void #{decl[:function]}_ExpectAndReturn(#{decl[:return]} toReturn);\n" + end + else + if decl[:return] == "void" + @header << "void #{decl[:function]}_Expect(#{decl[:args_no_var_args]});\n" + else + @header << "void #{decl[:function]}_ExpectAndReturn(#{decl[:args_no_var_args]}, #{decl[:return]} toReturn);\n" + end + end + + if (@use_cexception) + if decl[:args_no_var_args] == "void" + @header << "void #{decl[:function]}_ExpectAndThrow(int toThrow);\n" + else + @header << "void #{decl[:function]}_ExpectAndThrow(#{decl[:args_no_var_args]}, int toThrow);\n" + end + end + + if (@allow_ignore_mock) + if decl[:return] == "void" + @header << "void #{decl[:function]}_Ignore(void);\n" + else + @header << "void #{decl[:function]}_IgnoreAndReturn(#{decl[:return]} toReturn);\n" + end + end + end + + def create_mock_header_footer + @header << "\n#endif\n" + end + + def create_source_header_section + @source << "/* AUTOGENERATED FILE. DO NOT EDIT. */\n" + @source << "#include \n" + @source << "#include \n" + @source << "#include \n" + @source << "#include \"unity.h\"\n" + @source << "#include \"Exception.h\"\n" if (@use_cexception) + @includes.each {|include| @source << "#include \"#{include}\"\n"} + @source << "#include \"#{@mock_header_name_dest}\"\n\n" + end + + def create_instance_structure + @source << "static struct #{@mock_name}Instance\n" + @source << "{\n" + + if @parser.nondefine_functions.length == 0 + @source << "#{@tab}unsigned char placeHolder;\n" + end + + @source << "#{@tab}unsigned char allocFailure;\n" + + @parser.nondefine_functions.each do |function| + decl = parse_declaration(function) + + @source << "#{@tab}#{@call_count_type} #{decl[:function]}_CallCount;\n" + @source << "#{@tab}#{@call_count_type} #{decl[:function]}_CallsExpected;\n" + + if (@allow_ignore_mock) + @source << "#{@tab}#{@ignore_bool} #{decl[:function]}_IgnoreBool;\n" + end + + @source << "#{@tab}#{@call_count_type} *#{decl[:function]}_ThrowOnCallCount;\n" + @source << "#{@tab}#{@call_count_type} *#{decl[:function]}_ThrowOnCallCount_Head;\n" + @source << "#{@tab}#{@call_count_type} *#{decl[:function]}_ThrowOnCallCount_HeadTail;\n" + @source << "#{@tab}#{@throw_type} *#{decl[:function]}_ThrowValue;\n" + @source << "#{@tab}#{@throw_type} *#{decl[:function]}_ThrowValue_Head;\n" + @source << "#{@tab}#{@throw_type} *#{decl[:function]}_ThrowValue_HeadTail;\n" + + if (decl[:return] != "void") + @source << "#{@tab}#{decl[:return]} *#{decl[:function]}_Return;\n" + @source << "#{@tab}#{decl[:return]} *#{decl[:function]}_Return_Head;\n" + @source << "#{@tab}#{decl[:return]} *#{decl[:function]}_Return_HeadTail;\n" + end + + if (decl[:args_no_var_args] != "void") + args = parse_args(decl[:args_no_var_args]) + args.each do |arg| + type = arg[:type].sub(/const/, '').strip + @source << "#{@tab}#{type} *#{decl[:function]}_Expected_#{arg[:name]};\n" + @source << "#{@tab}#{type} *#{decl[:function]}_Expected_#{arg[:name]}_Head;\n" + @source << "#{@tab}#{type} *#{decl[:function]}_Expected_#{arg[:name]}_HeadTail;\n" + end + end + end + @source << "} Mock;\n\n" + end + + def create_extern_declarations + @parser.externs.each do |extern| + @source << extern.gsub(/extern/,'') << ";\n" + end + @source << "extern jmp_buf AbortFrame;\n" + @source << "\n" + end + + def create_mock_verify_function + @source << "void #{@mock_name}_Verify(void)\n{\n" + @source << "#{@tab}TEST_ASSERT_EQUAL(0, Mock.allocFailure);\n" + @parser.nondefine_functions.each do |function| + decl = parse_declaration(function) + @source << "#{@tab}TEST_ASSERT_EQUAL_MESSAGE(Mock.#{decl[:function]}_CallsExpected, Mock.#{decl[:function]}_CallCount, \"Function '#{decl[:function]}' called unexpected number of times.\");\n" + end + @source << "}\n\n" + end + + def create_mock_init_function + @source << "void #{@mock_name}_Init(void)\n{\n" + @source << "#{@tab}#{@mock_name}_Destroy();\n" + @source << "}\n\n" + end + + def create_mock_destroy_function + @source << "void #{@mock_name}_Destroy(void)\n{\n" + @parser.nondefine_functions.each do |function| + decl = parse_declaration(function) + + if decl[:return] != "void" + @source << "#{@tab}if(Mock.#{decl[:function]}_Return_Head)\n" + @source << "#{@tab}{\n" + @source << "#{@tab}#{@tab}free(Mock.#{decl[:function]}_Return_Head);\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_Return_Head=NULL;\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_Return_HeadTail=NULL;\n" + @source << "#{@tab}}\n" + end + if decl[:args_no_var_args] != "void" + args = parse_args(decl[:args_no_var_args]) + args.each do |arg| + @source << "#{@tab}if(Mock.#{decl[:function]}_Expected_#{arg[:name]}_Head)\n" + @source << "#{@tab}{\n" + @source << "#{@tab}#{@tab}free(Mock.#{decl[:function]}_Expected_#{arg[:name]}_Head);\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_Expected_#{arg[:name]}_Head=NULL;\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_Expected_#{arg[:name]}_HeadTail=NULL;\n" + @source << "#{@tab}}\n" + end + end + + @source << "#{@tab}if(Mock.#{decl[:function]}_ThrowOnCallCount_Head)\n" + @source << "#{@tab}{\n" + @source << "#{@tab}#{@tab}free(Mock.#{decl[:function]}_ThrowOnCallCount_Head);\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_ThrowOnCallCount_Head=NULL;\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_ThrowOnCallCount_HeadTail=NULL;\n" + @source << "#{@tab}}\n" + + @source << "#{@tab}if(Mock.#{decl[:function]}_ThrowValue_Head)\n" + @source << "#{@tab}{\n" + @source << "#{@tab}#{@tab}free(Mock.#{decl[:function]}_ThrowValue_Head);\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_ThrowValue_Head=NULL;\n" + @source << "#{@tab}#{@tab}Mock.#{decl[:function]}_ThrowValue_HeadTail=NULL;\n" + @source << "#{@tab}}\n" + end + @source << "#{@tab}memset(&Mock, 0, sizeof(Mock));\n" + @source << "}\n\n" + end + + def create_mock_argument_verifier(arguments) + @source << "void AssertParameters_#{decl[:function]}(#{decl[:args_no_var_args]})\n{\n" + arguments.each do |arg| + type = arg[:type].sub(/const/, '').strip + @source << make_handle_expected(decl[:function], type, arg[:name]) + end + @source << "}\n\n" + end + + def create_call_list(decl) + args = parse_args(decl[:args_no_var_args]) + call_list = "" + args.each do |arg| + if call_list.empty? + call_list = arg[:name] + else + call_list += ", " + arg[:name] + end + end + return call_list + end + + def create_mock_implementation(function) + + decl = parse_declaration(function) + + newtab = "#{@tab}" + + # Create mock argument verifier, if necessary + if decl[:args] != "void" + args = parse_args(decl[:args_no_var_args]) + create_mock_argument_verifier(args) + end + + # Create mock function + @source << "#{decl[:modifier]} " if (!decl[:modifier].nil? && decl[:modifier].length > 0) + @source << "#{decl[:return]} #{decl[:function]}(#{decl[:args]})\n" + @source << "{\n" + + # start ignore block + if (@allow_ignore_mock) + newtab = "#{@tab}#{@tab}" + @source << "#{@tab}if (!Mock.#{decl[:function]}_IgnoreBool)\n" + @source << "#{@tab}{\n" + end + + @source << "#{newtab}Mock.#{decl[:function]}_CallCount++;\n" + + #create overcall protection + exp = "Mock.#{decl[:function]}_CallsExpected" + @source << "#{newtab}if (Mock.#{decl[:function]}_CallCount > Mock.#{decl[:function]}_CallsExpected)\n" + @source << "#{newtab}{\n" + @source << "#{newtab}#{@tab}TEST_THROW(\"#{decl[:function]} Called More Times Than Expected\");\n" + @source << "#{newtab}}\n" + + # Create call to argument verifier, if necessary + if decl[:args_no_var_args] != "void" + @source << "#{newtab}AssertParameters_#{decl[:function]}(#{create_call_list(decl)});\n" + end + + # Throw exception, if appropriate + @source << make_handle_throw(decl[:function], @throw_type) if (@use_cexception) + + # end ignore block + if (@allow_ignore_mock) + @source << "#{@tab}}\n" + end + + # Return expected value, if necessary + if decl[:return] != "void" + @source << make_handle_return(decl[:function], decl[:return]) + end + + # Close out the function + @source << "}\n\n" + end + + def create_mock_function_expectation(function) + decl = parse_declaration(function) + if decl[:args_no_var_args] == "void" + # Function has void return type with no arguments + if decl[:return] == "void" + @source << "void #{decl[:function]}_Expect(void)\n" + @source << "{\n" + @source << "#{@tab}Mock.#{decl[:function]}_CallsExpected++;\n" + @source << "}\n\n" + # Function has non-void return type with no arguments + else + @source << "void #{decl[:function]}_ExpectAndReturn(#{decl[:return]} toReturn)\n" + @source << "{\n" + @source << "#{@tab}Mock.#{decl[:function]}_CallsExpected++;\n" + @source << make_expand_array(decl[:return], "Mock.#{decl[:function]}_Return_Head", "toReturn") + @source << "#{@tab}Mock.#{decl[:function]}_Return = Mock.#{decl[:function]}_Return_Head;\n" + @source << "#{@tab}Mock.#{decl[:function]}_Return += Mock.#{decl[:function]}_CallCount;\n" + @source << "}\n\n" + end + else + + # Parse function arguments + args = parse_args(decl[:args_no_var_args]) + + # Create parameter expectation function + @source << "void ExpectParameters_#{decl[:function]}(#{decl[:args_no_var_args]})\n" + @source << "{\n" + args.each do |arg| + type = arg[:type].sub(/const/, '').strip + @source << make_add_new_expected(decl[:function], type, arg[:name]) + end + @source << "}\n\n" + + # Function has void return type with arguments + if decl[:return] == "void" + @source << "void #{decl[:function]}_Expect(#{decl[:args_no_var_args]})\n" + @source << "{\n" + @source << "#{@tab}Mock.#{decl[:function]}_CallsExpected++;\n" + @source << "#{@tab}ExpectParameters_#{decl[:function]}(#{create_call_list(decl)});\n" + @source << "}\n\n" + # Function has non-void return type with arguments + else + @source << "void #{decl[:function]}_ExpectAndReturn(#{decl[:args_no_var_args]}, #{decl[:return]} toReturn)\n{\n" + @source << "#{@tab}Mock.#{decl[:function]}_CallsExpected++;\n" + @source << "#{@tab}ExpectParameters_#{decl[:function]}(#{create_call_list(decl)});\n" + @source << make_expand_array(decl[:return], "Mock.#{decl[:function]}_Return_Head", "toReturn") + @source << "#{@tab}Mock.#{decl[:function]}_Return = Mock.#{decl[:function]}_Return_Head;\n" + @source << "#{@tab}Mock.#{decl[:function]}_Return += Mock.#{decl[:function]}_CallCount;\n" + @source << "}\n\n" + end + end + end + + def create_mock_function_expectation_with_throw(function) + decl = parse_declaration(function) + + # function takes no arguments + if decl[:args_no_var_args] == "void" + @source << "void #{decl[:function]}_ExpectAndThrow(#{@throw_type} toThrow)\n" + @source << "{\n" + @source << "#{@tab}Mock.#{decl[:function]}_CallsExpected++;\n" + @source << make_expand_array(@call_count_type, "Mock.#{decl[:function]}_ThrowOnCallCount_Head", "Mock.#{decl[:function]}_CallsExpected") + @source << "#{@tab}Mock.#{decl[:function]}_ThrowOnCallCount = Mock.#{decl[:function]}_ThrowOnCallCount_Head;\n" + @source << "#{@tab}Mock.#{decl[:function]}_ThrowOnCallCount += Mock.#{decl[:function]}_CallCount;\n" + @source << make_expand_array(@throw_type, "Mock.#{decl[:function]}_ThrowValue_Head", "toThrow") + @source << "#{@tab}Mock.#{decl[:function]}_ThrowValue = Mock.#{decl[:function]}_ThrowValue_Head;\n" + @source << "#{@tab}Mock.#{decl[:function]}_ThrowValue += Mock.#{decl[:function]}_CallCount;\n" + @source << "}\n\n" + else + # Parse function arguments + args = parse_args(decl[:args_no_var_args]) + + @source << "void #{decl[:function]}_ExpectAndThrow(#{decl[:args_no_var_args]}, #{@throw_type} toThrow)\n" + @source << "{\n" + @source << "#{@tab}Mock.#{decl[:function]}_CallsExpected++;\n" + @source << make_expand_array(@call_count_type, "Mock.#{decl[:function]}_ThrowOnCallCount_Head", "Mock.#{decl[:function]}_CallsExpected") + @source << "#{@tab}Mock.#{decl[:function]}_ThrowOnCallCount = Mock.#{decl[:function]}_ThrowOnCallCount_Head;\n" + @source << "#{@tab}Mock.#{decl[:function]}_ThrowOnCallCount += Mock.#{decl[:function]}_CallCount;\n" + @source << make_expand_array(@throw_type, "Mock.#{decl[:function]}_ThrowValue_Head", "toThrow") + @source << "#{@tab}Mock.#{decl[:function]}_ThrowValue = Mock.#{decl[:function]}_ThrowValue_Head;\n" + @source << "#{@tab}Mock.#{decl[:function]}_ThrowValue += Mock.#{decl[:function]}_CallCount;\n" + @source << "#{@tab}ExpectParameters_#{decl[:function]}(#{create_call_list(decl)});\n" + @source << "}\n\n" + end + end + + def create_mock_function_ignore(function) + decl = parse_declaration(function) + # Function has void return type with no arguments + if decl[:return] == "void" + @source << "void #{decl[:function]}_Ignore(void)\n" + @source << "{\n" + @source << "#{@tab}Mock.#{decl[:function]}_IgnoreBool = (unsigned char)1;\n" + @source << "}\n\n" + # Function has non-void return type with no arguments + else + @source << "void #{decl[:function]}_IgnoreAndReturn(#{decl[:return]} toReturn)\n" + @source << "{\n" + @source << "#{@tab}Mock.#{decl[:function]}_IgnoreBool = (unsigned char)1;\n" + @source << make_expand_array(decl[:return], "Mock.#{decl[:function]}_Return_Head", "toReturn") + @source << "#{@tab}Mock.#{decl[:function]}_Return = Mock.#{decl[:function]}_Return_Head;\n" + @source << "#{@tab}Mock.#{decl[:function]}_Return += Mock.#{decl[:function]}_CallCount;\n" + @source << "}\n\n" + end + end + + def make_expand_array(type, array, newValue) + < name, :type => type} + end + return args + end + + def parse_declaration(declaration) + decl = {} + + @declaration_parse_matcher.match(declaration) + + modifier = $1 + modifier = '' if modifier.nil? + decl[:modifier] = modifier.strip + + decl[:return] = $2 + + decl[:function] = $3 + + args = $4 + #remove default parameter statements from mock definitions + args.gsub!(/=\s*[a-zA-Z0-9_\.]+\s*\,/, ',') + decl_args.gsub!(/=\s*[a-zA-Z0-9_\.]+\s*/, ' ') + decl_args.strip! + decl[:args] = decl_args + + # ignore variable arguments at end of parameter list + if (decl_args == @var_args_ellipsis) + decl[:args_no_var_args] = 'void' + else + decl[:args_no_var_args] = decl_args.sub(/,\s*\.\.\./, '') + end + + if decl[:return].nil? or decl[;function].nil? or decl[:args].nil? + raise "Declaration parse failed!\n" + + " declaration: #{declaration}\n" + + " modifier: #{decl[:modifier]}\n" + + " return: #{decl[:return]}\n" + + " function: #{decl[:function]}\n" + + " args:#{decl[:args]}\n" + + " args_no_var_args:#{decl[:args_no_var_args]}" + end + + return decl + end + +end diff --git a/lib/cmock_new.rb b/lib/cmock_new.rb new file mode 100644 index 0000000..eb77c4c --- /dev/null +++ b/lib/cmock_new.rb @@ -0,0 +1,15 @@ + +class CMock + attr_accessor :mocks_path, :includes, :interface_parser + + def initialize(mocks_path='mocks', includes=[], interface_parser=nil) + @mocks_path = mocks_path + @includes = includes + @interface_parser = interface_parser + end + + def generate(module_header) + @interface_parser.extract_interface(module_header) + end + +end \ No newline at end of file diff --git a/lib/cmock_setup.rb b/lib/cmock_setup.rb new file mode 100644 index 0000000..c97faaa --- /dev/null +++ b/lib/cmock_setup.rb @@ -0,0 +1,31 @@ +$here = File.dirname __FILE__ +require "#{$here}/cmock_generator" + +class CMockSetup + + attr_accessor :mocks_path, :auto_path + + def initialize(mocks_path='mocks', includes=[], use_cexception=true, allow_ignore_mock=false) + @mocks_path = mocks_path + @includes = includes + @use_cexception = use_cexception + @allow_ignore_mock = allow_ignore_mock + end + + def setup_mocks(files) + files.each do |src| + generate_mock src + end + end + + private + + def generate_mock(src) + name = File.basename(src, '.h') + path = File.dirname(src) + cmg = CMockGenerator.new(name, path, @mocks_path, @includes, @use_cexception, @allow_ignore_mock) + $stderr.puts "Creating mock for #{name}..." + $stderr.flush + cmg.create_mock + end +end