mirror of
https://github.com/ThrowTheSwitch/CMock.git
synced 2026-06-06 05:25:29 +00:00
add support for mocking C++ static class member methods
This commit is contained in:
+27
-4
@@ -65,6 +65,10 @@ class CMockGenerator
|
||||
@file_writer.create_subdir(@subdir)
|
||||
end
|
||||
|
||||
def create_using_statement(file, function)
|
||||
file << "using namespace #{function[:namespace].join('::')};\n" unless function[:namespace].empty?
|
||||
end
|
||||
|
||||
def create_mock_header_file(parsed_stuff)
|
||||
if @include_inline == :include
|
||||
@file_writer.create_file(@module_name + (@module_ext || '.h'), @subdir) do |file, _filename|
|
||||
@@ -77,6 +81,7 @@ class CMockGenerator
|
||||
create_mock_header_service_call_declarations(file)
|
||||
create_typedefs(file, parsed_stuff[:typedefs])
|
||||
parsed_stuff[:functions].each do |function|
|
||||
create_using_statement(file, function)
|
||||
file << @plugins.run(:mock_function_declarations, function)
|
||||
end
|
||||
create_mock_header_footer(file)
|
||||
@@ -253,15 +258,26 @@ class CMockGenerator
|
||||
args_string = function[:args_string]
|
||||
args_string += (', ' + function[:var_arg]) unless function[:var_arg].nil?
|
||||
|
||||
# Encapsulate in namespace(s) if applicable
|
||||
function[:namespace].each do |ns|
|
||||
file << "namespace #{ns} {\n"
|
||||
end
|
||||
|
||||
# Determine class prefix (if any)
|
||||
cls_pre = ''
|
||||
unless function[:class].nil?
|
||||
cls_pre = "#{function[:class]}::"
|
||||
end
|
||||
|
||||
# Create mock function
|
||||
unless @weak.empty?
|
||||
file << "#if defined (__IAR_SYSTEMS_ICC__)\n"
|
||||
file << "#pragma weak #{function[:name]}\n"
|
||||
file << "#pragma weak #{function[:unscoped_name]}\n"
|
||||
file << "#else\n"
|
||||
file << "#{function_mod_and_rettype} #{function[:name]}(#{args_string}) #{weak};\n"
|
||||
file << "#{function_mod_and_rettype} #{function[:unscoped_name]}(#{args_string}) #{weak};\n"
|
||||
file << "#endif\n\n"
|
||||
end
|
||||
file << "#{function_mod_and_rettype} #{function[:name]}(#{args_string})\n"
|
||||
file << "#{function_mod_and_rettype} #{cls_pre}#{function[:unscoped_name]}(#{args_string})\n"
|
||||
file << "{\n"
|
||||
file << " UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;\n"
|
||||
file << " CMOCK_#{function[:name]}_CALL_INSTANCE* cmock_call_instance;\n"
|
||||
@@ -280,7 +296,14 @@ class CMockGenerator
|
||||
file << @plugins.run(:mock_implementation, function)
|
||||
file << " UNITY_CLR_DETAILS();\n"
|
||||
file << " return cmock_call_instance->ReturnVal;\n" unless function[:return][:void?]
|
||||
file << "}\n\n"
|
||||
file << "}\n"
|
||||
|
||||
# Close any namespace(s) opened above
|
||||
function[:namespace].each do
|
||||
file << "}\n"
|
||||
end
|
||||
|
||||
file << "\n"
|
||||
end
|
||||
|
||||
def create_mock_interfaces(file, function)
|
||||
|
||||
+81
-10
@@ -37,8 +37,10 @@ class CMockHeaderParser
|
||||
@normalized_source = nil
|
||||
function_names = []
|
||||
|
||||
parse_functions(import_source(source)).map do |decl|
|
||||
func = parse_declaration(decl)
|
||||
all_funcs = parse_functions(import_source(source)).map { |item| [item] }
|
||||
all_funcs += parse_cpp_functions(import_source(source, true))
|
||||
all_funcs.map do |decl|
|
||||
func = parse_declaration(*decl)
|
||||
unless function_names.include? func[:name]
|
||||
@funcs << func
|
||||
function_names << func[:name]
|
||||
@@ -180,7 +182,7 @@ class CMockHeaderParser
|
||||
source
|
||||
end
|
||||
|
||||
def import_source(source)
|
||||
def import_source(source, cpp = false)
|
||||
# let's clean up the encoding in case they've done anything weird with the characters we might find
|
||||
source = source.force_encoding('ISO-8859-1').encode('utf-8', :replace => nil)
|
||||
|
||||
@@ -222,7 +224,10 @@ class CMockHeaderParser
|
||||
# forward declared structs are removed before struct definitions so they don't mess up real thing later. we leave structs keywords in function prototypes
|
||||
source.gsub!(/^[\w\s]*struct[^;\{\}\(\)]+;/m, '') # remove forward declared structs
|
||||
source.gsub!(/^[\w\s]*(enum|union|struct|typedef)[\w\s]*\{[^\}]+\}[\w\s\*\,]*;/m, '') # remove struct, union, and enum definitions and typedefs with braces
|
||||
source.gsub!(/(\W)(?:register|auto|static|restrict)(\W)/, '\1\2') # remove problem keywords
|
||||
# remove problem keywords
|
||||
source.gsub!(/(\W)(?:register|auto|restrict)(\W)/, '\1\2')
|
||||
source.gsub!(/(\W)(?:static)(\W)/, '\1\2') unless cpp
|
||||
|
||||
source.gsub!(/\s*=\s*['"a-zA-Z0-9_\.]+\s*/, '') # remove default value statements from argument lists
|
||||
source.gsub!(/^(?:[\w\s]*\W)?typedef\W[^;]*/m, '') # remove typedef statements
|
||||
source.gsub!(/\)(\w)/, ') \1') # add space between parenthese and alphanumeric
|
||||
@@ -234,11 +239,13 @@ class CMockHeaderParser
|
||||
# scan for functions which return function pointers, because they are a pain
|
||||
source.gsub!(/([\w\s\*]+)\(*\(\s*\*([\w\s\*]+)\s*\(([\w\s\*,]*)\)\)\s*\(([\w\s\*,]*)\)\)*/) do |_m|
|
||||
functype = "cmock_#{@module_name}_func_ptr#{@typedefs.size + 1}"
|
||||
@typedefs << "typedef #{Regexp.last_match(1).strip}(*#{functype})(#{Regexp.last_match(4)});"
|
||||
"#{functype} #{Regexp.last_match(2).strip}(#{Regexp.last_match(3)});"
|
||||
unless cpp # only collect once
|
||||
@typedefs << "typedef #{Regexp.last_match(1).strip}(*#{functype})(#{Regexp.last_match(4)});"
|
||||
"#{functype} #{Regexp.last_match(2).strip}(#{Regexp.last_match(3)});"
|
||||
end
|
||||
end
|
||||
|
||||
source = remove_nested_pairs_of_braces(source)
|
||||
source = remove_nested_pairs_of_braces(source) unless cpp
|
||||
|
||||
if @treat_inlines == :include
|
||||
# Functions having "{ }" at this point are/were inline functions,
|
||||
@@ -257,7 +264,8 @@ class CMockHeaderParser
|
||||
source.gsub!(/\s+/, ' ') # remove remaining extra white space
|
||||
|
||||
# split lines on semicolons and remove things that are obviously not what we are looking for
|
||||
src_lines = source.split(/\s*;\s*/).uniq
|
||||
src_lines = source.split(/\s*;\s*/)
|
||||
src_lines = src_lines.uniq unless cpp # must retain closing braces for class/namespace
|
||||
src_lines.delete_if { |line| line.strip.empty? } # remove blank lines
|
||||
src_lines.delete_if { |line| !(line =~ /[\w\s\*]+\(+\s*\*[\*\s]*[\w\s]+(?:\[[\w\s]*\]\s*)+\)+\s*\((?:[\w\s\*]*,?)*\s*\)/).nil? } # remove function pointer arrays
|
||||
|
||||
@@ -272,6 +280,55 @@ class CMockHeaderParser
|
||||
src_lines.delete_if(&:empty?) # drop empty lines
|
||||
end
|
||||
|
||||
# Rudimentary C++ parser - does not handle all situations - e.g.:
|
||||
# * A namespace function appears after a class with private members (should be parsed)
|
||||
# * Anonymous namespace (shouldn't parse anything - no matter how nested - within it)
|
||||
# * A class nested within another class
|
||||
def parse_cpp_functions(source)
|
||||
funcs = []
|
||||
|
||||
ns = []
|
||||
pub = false
|
||||
source.each do |line|
|
||||
# Search for namespace, class, opening and closing braces
|
||||
line.scan(/(?:(?:\b(?:namespace|class)\s+(?:\S+)\s*)?{)|}/).each do |item|
|
||||
if item == '}'
|
||||
ns.pop
|
||||
else
|
||||
token = item.strip.sub(/\s+/, ' ')
|
||||
ns << token
|
||||
|
||||
pub = false if token.start_with? 'class'
|
||||
pub = true if token.start_with? 'namespace'
|
||||
end
|
||||
end
|
||||
|
||||
pub = true if line =~ /public:/
|
||||
pub = false if line =~ /private:/ || line =~ /protected:/
|
||||
|
||||
# ignore non-public and non-static
|
||||
next unless pub
|
||||
next unless line =~ /\bstatic\b/
|
||||
|
||||
line.sub!(/^.*static/, '')
|
||||
next unless line =~ @declaration_parse_matcher
|
||||
|
||||
tmp = ns.reject { |item| item == '{' }
|
||||
|
||||
# Identify class name, if any
|
||||
cls = nil
|
||||
if tmp[-1].start_with? 'class '
|
||||
cls = tmp.pop.sub(/class (\S+) {/, '\1')
|
||||
end
|
||||
|
||||
# Assemble list of namespaces
|
||||
tmp.each { |item| item.sub!(/(?:namespace|class) (\S+) {/, '\1') }
|
||||
|
||||
funcs << [line.strip.gsub(/\s+/, ' '), tmp, cls]
|
||||
end
|
||||
funcs
|
||||
end
|
||||
|
||||
def parse_functions(source)
|
||||
funcs = []
|
||||
source.each { |line| funcs << line.strip.gsub(/\s+/, ' ') if line =~ @declaration_parse_matcher }
|
||||
@@ -442,8 +499,10 @@ class CMockHeaderParser
|
||||
end
|
||||
end
|
||||
|
||||
def parse_declaration(declaration)
|
||||
def parse_declaration(declaration, namespace = [], classname = nil)
|
||||
decl = {}
|
||||
decl[:namespace] = namespace
|
||||
decl[:class] = classname
|
||||
|
||||
regex_match = @declaration_parse_matcher.match(declaration)
|
||||
raise "Failed parsing function declaration: '#{declaration}'" if regex_match.nil?
|
||||
@@ -454,7 +513,19 @@ class CMockHeaderParser
|
||||
# process function attributes, return type, and name
|
||||
parsed = parse_type_and_name(regex_match[1])
|
||||
|
||||
decl[:name] = parsed[:name]
|
||||
# Record original name without scope prefix
|
||||
decl[:unscoped_name] = parsed[:name]
|
||||
|
||||
# Prefix name with namespace scope (if any) and then class
|
||||
decl[:name] = namespace.join('_')
|
||||
unless classname.nil?
|
||||
decl[:name] << '_' unless decl[:name].empty?
|
||||
decl[:name] << classname
|
||||
end
|
||||
# Add original name to complete fully scoped name
|
||||
decl[:name] << '_' unless decl[:name].empty?
|
||||
decl[:name] << decl[:unscoped_name]
|
||||
|
||||
decl[:modifier] = parsed[:modifier]
|
||||
unless parsed[:c_calling_convention].nil?
|
||||
decl[:c_calling_convention] = parsed[:c_calling_convention]
|
||||
|
||||
Reference in New Issue
Block a user