Merge branch 'master' into master

This commit is contained in:
Mark VanderVoord
2020-03-16 13:44:41 -04:00
committed by GitHub
13 changed files with 401 additions and 26 deletions
+23 -7
View File
@@ -282,6 +282,22 @@ You may specify the options explicitly:
cmock = Cmock.new(:plugins => [:cexception, :ignore], :mock_path => 'my/mocks/')
Creating Skeletons:
-------------------
Not only is CMock able to generate mock files from a header file, but it is also able
to generate (and update) skeleton C files from headers. It does this by creating a
(mostly) empty implementation for every function that is declared in the header. If you later
add to that header list, just run this feature again and it will add prototypes for the missing
functions!
Like the normal usecase for CMock, this feature can be used from the command line
or from within its ruby API. For example, from the command line, add `--skeleton` to
generate a skeleton instead:
```
ruby cmock.rb --skeleton ../create/c/for/this.h
```
Config Options:
---------------
@@ -394,14 +410,14 @@ from the defaults. We've tried to specify what the defaults are below.
* default: mocks
* `:mock_prefix`:
The prefix to prepend to your mock files. For example, if it's Mock, a file
The prefix to prepend to your mock files. For example, if it's `Mock`, a file
“USART.h” will get a mock called “MockUSART.c”. This CAN be used with a suffix
at the same time.
* default: Mock
* `:mock_suffix`:
The suffix to append to your mock files. For example, it it's "_Mock", a file
The suffix to append to your mock files. For example, it it's `_Mock`, a file
"USART.h" will get a mock called "USART_Mock.h". This CAN be used with a prefix
at the same time.
@@ -637,8 +653,8 @@ from the defaults. We've tried to specify what the defaults are below.
Parameters must match *both* `:array_size_type` and `:array_size_name` (and
must come right after a pointer parameter) to be treated as an array size.
Once you've told it how to recognize your arrays, CMock will give you _Expect
calls that work more like _ExpectWithArray, and compare an array of objects
Once you've told it how to recognize your arrays, CMock will give you `_Expect`
calls that work more like `_ExpectWithArray`, and compare an array of objects
rather than just a single object.
For example, if you write the following, CMock will check that GoBananas is
@@ -653,14 +669,14 @@ from the defaults. We've tried to specify what the defaults are below.
GoBananas_ExpectWithArray(b, 2, 2);
* `:fail_on_unexpected_calls`:
By default, CMock will fail a test if a mock is called without _Expect and _Ignore
By default, CMock will fail a test if a mock is called without `_Expect` and `_Ignore`
called first. While this forces test writers to be more explicit in their expectations,
it can clutter tests with _Expect or _Ignore calls for functions which are not the focus
it can clutter tests with `_Expect` or `_Ignore` calls for functions which are not the focus
of the test. While this is a good indicator that this module should be refactored, some
users are not fans of the additional noise.
Therefore, :fail_on_unexpected_calls can be set to false to force all mocks to start with
the assumption that they are operating as _Ignore unless otherwise specified.
the assumption that they are operating as `_Ignore` unless otherwise specified.
* default: true
* **note:**
+19 -1
View File
@@ -32,6 +32,12 @@ class CMock
end
end
def setup_skeletons(files)
[files].flatten.each do |src|
generate_skeleton src
end
end
private ###############################
def generate_mock(src)
@@ -39,6 +45,12 @@ class CMock
puts "Creating mock for #{name}..." unless @silent
@cm_generator.create_mock(name, @cm_parser.parse(name, File.read(src)))
end
def generate_skeleton(src)
name = File.basename(src, '.h')
puts "Creating skeleton for #{name}..." unless @silent
@cm_generator.create_skeleton(name, @cm_parser.parse(name, File.read(src)))
end
end
def option_maker(options, key, val)
@@ -75,6 +87,8 @@ if ($0 == __FILE__)
ARGV.each do |arg|
if (arg =~ /^-o\"?([a-zA-Z0-9._\\\/:\s]+)\"?/)
options.merge! CMockConfig.load_config_file_from_yaml( arg.gsub(/^-o/,'') )
elsif (arg == "--skeleton")
options[:skeleton] = true
elsif (arg =~ /^--([a-zA-Z0-9._\\\/:\s]+)=\"?([a-zA-Z0-9._\-\\\/:\s\;]+)\"?/)
options = option_maker(options, $1, $2)
else
@@ -82,5 +96,9 @@ if ($0 == __FILE__)
end
end
CMock.new(options).setup_mocks(filelist)
if (options[:skeleton])
CMock.new(options).setup_skeletons(filelist)
else
CMock.new(options).setup_mocks(filelist)
end
end
+2
View File
@@ -12,6 +12,7 @@ class CMockConfig
:mock_path => 'mocks',
:mock_prefix => 'Mock',
:mock_suffix => '',
:skeleton_path => '',
:weak => '',
:subdir => nil,
:plugins => [],
@@ -40,6 +41,7 @@ class CMockConfig
:orig_header_include_fmt => "#include \"%s\"",
:array_size_type => [],
:array_size_name => 'size|len',
:skeleton => false,
# Format to look for inline functions.
# This is a combination of "static" and "inline" keywords ("static inline", "inline static", "inline", "static")
+8
View File
@@ -33,6 +33,14 @@ class CMockFileWriter
update_file(full_file_name_done, full_file_name_temp)
end
def append_file(filename, subdir)
raise "Where's the block of data to create?" unless block_given?
full_file_name = "#{@config.skeleton_path}/#{subdir+'/' if subdir}#{filename}"
File.open(full_file_name, 'a') do |file|
yield(file, filename)
end
end
private ###################################
def update_file(dest, src)
+37 -1
View File
@@ -55,6 +55,11 @@ class CMockGenerator
create_mock_source_file(parsed_stuff)
end
def create_skeleton(module_name, parsed_stuff)
@module_name = module_name
create_skeleton_source_file(parsed_stuff)
end
private if $ThisIsOnlyATest.nil? ##############################
def create_mock_subdir()
@@ -94,6 +99,17 @@ class CMockGenerator
end
end
def create_skeleton_source_file(parsed_stuff)
filename = "#{@config.mock_path}/#{@subdir+'/' if @subdir}#{module_name}.c"
existing = File.exists?(filename) ? File.read(filename) : ""
@file_writer.append_file(@module_name + ".c", @subdir) do |file, filename|
create_source_header_section(file, filename, []) if existing.empty?
parsed_stuff[:functions].each do |function|
create_function_skeleton(file, function, existing)
end
end
end
def create_mock_header_header(file, filename)
define_name = @clean_mock_name.upcase
orig_filename = (@subdir ? @subdir + "/" : "") + @module_name + ".h"
@@ -146,7 +162,7 @@ class CMockGenerator
def create_source_header_section(file, filename, functions)
header_file = (@subdir ? @subdir + '/' : '') + filename.gsub(".c",".h")
file << "/* AUTOGENERATED FILE. DO NOT EDIT. */\n"
file << "/* AUTOGENERATED FILE. DO NOT EDIT. */\n" unless functions.empty?
file << "#include <string.h>\n"
file << "#include <stdlib.h>\n"
file << "#include <setjmp.h>\n"
@@ -272,4 +288,24 @@ class CMockGenerator
file << @utils.code_add_argument_loader(function)
file << @plugins.run(:mock_interfaces, function)
end
def create_function_skeleton(file, function, existing)
# prepare return value and arguments
function_mod_and_rettype = (function[:modifier].empty? ? '' : "#{function[:modifier]} ") +
(function[:return][:type]) +
(function[:c_calling_convention] ? " #{function[:c_calling_convention]}" : '')
args_string = function[:args_string]
args_string += (", " + function[:var_arg]) unless (function[:var_arg].nil?)
decl = "#{function_mod_and_rettype} #{function[:name]}(#{args_string})"
unless (existing.include?(decl))
file << "#{decl}\n"
file << "{\n"
file << " //TODO: Implement Me!\n"
function[:args].each {|arg| file << " (void)#{arg[:name]};\n"}
file << " return (#{(function[:return][:type])})0;\n" unless (function[:return][:void?])
file << "}\n\n"
end
end
end
+7
View File
@@ -79,4 +79,11 @@ class CMockGeneratorPluginCallback
lines << " Mock.#{func_name}_CallbackBool = (int)0;\n"
lines << " Mock.#{func_name}_CallbackFunctionPointer = Callback;\n}\n\n"
end
def mock_verify(function)
func_name = function[:name]
" if (Mock.#{func_name}_CallbackFunctionPointer != NULL)\n {\n" \
" call_instance = CMOCK_GUTS_NONE;\n" \
" (void)call_instance;\n }\n"
end
end
+46 -13
View File
@@ -16,7 +16,8 @@ class CMockHeaderParser
@c_calling_conventions = cfg.c_calling_conventions.uniq
@treat_as_array = cfg.treat_as_array
@treat_as_void = (['void'] + cfg.treat_as_void).uniq
@declaration_parse_matcher = /([\w\s\*\(\),\[\]]+??)\(([\w\s\*\(\),\.\[\]+-]*)\)$/m
@function_declaration_parse_base_match = '([\w\s\*\(\),\[\]]+??)\(([\w\s\*\(\),\.\[\]+-]*)\)'
@declaration_parse_matcher = /#{@function_declaration_parse_base_match}$/m
@standards = (['int','short','char','long','unsigned','signed'] + cfg.treat_as.keys).uniq
@array_size_name = cfg.array_size_name
@array_size_type = (['int', 'size_t'] + cfg.array_size_type).uniq
@@ -107,31 +108,63 @@ class CMockHeaderParser
square_bracket_pair_regex_format = /\{[^\{\}]*\}/ # Regex to match one whole block enclosed by two square brackets
# Convert user provided string patterns to regex
# Use word bounderies before and after the user regex to limit matching to actual word iso part of a word
@inline_function_patterns.each do |user_format_string|
user_regex = Regexp.new(user_format_string)
cleanup_spaces_after_user_regex = /\s*/
inline_function_regex_formats << Regexp.new(user_regex.source + cleanup_spaces_after_user_regex.source)
word_boundary_before_user_regex = /\b/
cleanup_spaces_after_user_regex = /[ ]*\b/
inline_function_regex_formats << Regexp.new(word_boundary_before_user_regex.source + user_regex.source + cleanup_spaces_after_user_regex.source)
end
# 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)
# - Just looking for static|inline in the gsub is a bit too aggressive (functions that are named like this, ...), so we try to be a bit smarter
# Instead, look for "static inline" and parse it:
# - Everything before the match should just be copied, we don't want
# to touch anything but the inline functions.
# - Remove the implementation of the inline function (this is enclosed
# in square brackets) and replace it with ";" to complete the
# transformation to normal/non-inline function.
# To ensure proper removal of the function body, we count the number of square-bracket pairs
# and remove the pairs one-by-one.
# - Copy everything after the inline function implementation and start the parsing of the next inline function
# smush multiline macros into single line (checking for continuation character at end of line '\')
# If the user uses a macro to declare an inline function,
# smushing the macros makes it easier to recognize them as a macro and if required,
# remove them later on in this function
source.gsub!(/\s*\\\s*/m, ' ')
# Just looking for static|inline in the gsub is a bit too aggressive (functions that are named like this, ...), so we try to be a bit smarter
# Instead, look for an inline pattern (f.e. "static inline") and parse it.
# Below is a small explanation on how the general mechanism works:
# - Everything before the match should just be copied, we don't want
# to touch anything but the inline functions.
# - Remove the implementation of the inline function (this is enclosed
# in square brackets) and replace it with ";" to complete the
# transformation to normal/non-inline function.
# To ensure proper removal of the function body, we count the number of square-bracket pairs
# and remove the pairs one-by-one.
# - Copy everything after the inline function implementation and start the parsing of the next inline function
# There are ofcourse some special cases (inline macro declarations, inline function declarations, ...) which are handled and explained below
inline_function_regex_formats.each do |format|
loop do
inline_function_match = source.match(/#{format}/) # Search for inline function declaration
break if nil == inline_function_match # No inline functions so nothing to do
# 1. Determine if we are dealing with a user defined macro to declare inline functions
# If the end of the pre-match string is a macro-declaration-like string,
# we are dealing with a user defined macro to declare inline functions
if /(#define\s*)\z/ === inline_function_match.pre_match
# Remove the macro from the source
stripped_pre_match = inline_function_match.pre_match.sub(/(#define\s*)\z/,'')
stripped_post_match = inline_function_match.post_match.sub(/\A(.*[\n]?)/,'')
source = stripped_pre_match + stripped_post_match
next
end
# 2. Determine if we are dealing with an inline function declaration iso function definition
# If the start of the post-match string is a function-declaration-like string (something ending with semicolon after the function arguments),
# we are dealing with a inline function declaration
if /\A#{@function_declaration_parse_base_match}\s*;/m === inline_function_match.post_match
# Only remove the inline part from the function declaration, leaving the function declaration won't do any harm
source = inline_function_match.pre_match + inline_function_match.post_match
next
end
# 3. If we get here, we found an inline function declaration AND inline function body.
# Remove the function body to transform it into a 'normal' function.
total_pairs_to_remove = count_number_of_pairs_of_braces_in_function(inline_function_match.post_match)
break if 0 == total_pairs_to_remove # Bad source?
+1 -1
View File
@@ -92,7 +92,7 @@ namespace :test do
#individual system tests
FileList['system/test_interactions/*.yml'].each do |test|
basename = File.basename(test,'.*')
desc "Run system test #{basename}"
#desc "Run system test #{basename}"
task basename do
run_system_test_interactions([test])
end
+14 -2
View File
@@ -66,6 +66,7 @@ class SystemTestGenerator
generate_test_file(yaml_hash, namix, name)
generate_source_file(yaml_hash, namix, name)
generate_skeleton_file(yaml_hash, namix, name)
end
def generate_types_file(yaml_hash, namix)
@@ -136,11 +137,22 @@ class SystemTestGenerator
includes << (namix + MOCKABLE_H) if not yaml_hash[:systest][:mockable].nil?
includes << header_file
write_source_file(GENERATED_PATH + name + C_EXTENSION, includes.flatten) do |out|
out.puts(source[:code])
unless (source[:code].nil?)
write_source_file(GENERATED_PATH + name + C_EXTENSION, includes.flatten) do |out|
out.puts(source[:code])
end
end
end
def generate_skeleton_file(yaml_hash, namix, name)
source = yaml_hash[:systest][:skeleton]
return if source.nil?
require 'cmock.rb'
cmock = CMock.new(GENERATED_PATH + namix + 'cmock' + YAML_EXTENSION)
cmock.setup_skeletons("#{$cfg['compiler']['source_path']}#{name}.h")
end
def write_header_file(filename, upcase_name, include_list=[])
File.open(filename, 'w') do |out|
out.puts("#ifndef _#{upcase_name}")
@@ -0,0 +1,55 @@
---
:cmock:
:plugins:
- # none
:skeleton_path: system/generated
:systest:
:types: |
#define UINT32 unsigned int
:mockable: |
UINT32 something(int a);
:skeleton: skeleton.h
:source:
:header: |
void function_a(void);
int function_b(int a, int b);
const char* function_c(void);
# we are purposefully not including a :code section because it will be generated with skeleton
:tests:
:common: |
void setUp(void) {}
void tearDown(void) {}
:units:
- :pass: TRUE
:should: 'generate an empty shell for functions with no return values'
:code: |
test()
{
function_a();
}
- :pass: TRUE
:should: 'return numerical zero for numerical return values'
:code: |
test()
{
TEST_ASSERT_EQUAL_INT(0, function_b(1, 2));
}
- :pass: TRUE
:should: 'return null for pointer return values'
:code: |
test()
{
TEST_ASSERT_NULL(function_c());
}
...
@@ -0,0 +1,76 @@
---
:cmock:
:plugins:
- # none
:skeleton_path: system/generated
:systest:
:types: |
#define UINT32 unsigned int
:mockable: |
UINT32 something(int a);
:skeleton: skeleton_update.h
:source:
:header: |
void function_a(void);
int function_b(int a, int b);
const char* function_c(void);
# note that this code section exists and will be updated by the skeleton generator
:code: |
const char* donuts = "donuts";
const char* function_c(void)
{
return donuts;
}
int function_d(void)
{
return 77;
}
:tests:
:common: |
void setUp(void) {}
void tearDown(void) {}
extern int function_d();
:units:
- :pass: TRUE
:should: 'generate an empty shell for functions with no return values'
:code: |
test()
{
function_a();
}
- :pass: TRUE
:should: 'return numerical zero for numerical return values'
:code: |
test()
{
TEST_ASSERT_EQUAL_INT(0, function_b(1, 2));
}
- :pass: TRUE
:should: 'not overwrite functions that already exist'
:code: |
test()
{
TEST_ASSERT_EQUAL_STRING("donuts", function_c());
}
- :pass: TRUE
:should: 'leave functions it has never heard of'
:code: |
test()
{
TEST_ASSERT_EQUAL_INT(77, function_d());
}
...
+112
View File
@@ -2083,4 +2083,116 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do
assert_equal(expected, @parser.parse("module", source)[:functions])
end
it "Transform inline functions can handle inline function declarations" do
source =
"static inline int dummy_func_decl(int a, char b, float c);\n" + # First declaration
"static inline int dummy_func_decl2(int a, char b, float c)\n\n\n\n\n\n;\n" + # Second declaration with a lot of newlines before the semicolon to mess with the parser
"static inline int staticinlinefunc(struct my_struct *s)\n" + # 'normal' inline pattern
"{\n" +
" return dummy_func_decl(1, 1, 1);\n" +
"}\n" +
"struct my_struct_with_inline_in_it\n" # struct definition in between to mess with the parser
"{\n" +
" int a;\n" +
" char b;\n" +
" float inlineb;\n" +
"};\n" +
"static inline int dummy_func_decl(int a, char b, float c) {\n" + # Second user pattern
" return 42;\n" +
"}\n" +
"\n"
expected =
"int dummy_func_decl(int a, char b, float c);\n" +
"int dummy_func_decl2(int a, char b, float c)\n\n\n\n\n\n;\n" + # Second declaration with a lot of newlines until the semicolon to mess with the parser
"int staticinlinefunc(struct my_struct *s);\n" +
"struct my_struct_with_inline_in_it\n"
"{\n" +
" int a;\n" +
" char b;\n" +
" float inlineb;\n" +
"};\n" +
"int dummy_func_decl(int a, char b, float c);\n" +
"\n"
@parser.treat_inlines = :include
assert_equal(expected, @parser.transform_inline_functions(source))
end
it "Transform inline functions can handle header with only inline function declarations" do
source =
"static inline int dummy_func_decl(int a, char b, float c);\n" +
"\n"
expected =
"int dummy_func_decl(int a, char b, float c);\n" +
"\n"
@parser.treat_inlines = :include
assert_equal(expected, @parser.transform_inline_functions(source))
end
it "Transform inline functions takes user provided patterns into account" do
source =
"static __inline__ __attribute__ ((always_inline)) uint16_t _somefunc (uint32_t a)\n" +
"{\n" +
" return _someotherfunc (a);\n" +
"}\n" +
"static __inline__ uint16_t _somefunc_0 (uint32_t a)\n" +
"{\n" +
" return (uint16_t) a;\n" +
"}\n" +
"\n"
expected =
"uint16_t _somefunc (uint32_t a);\n" +
"uint16_t _somefunc_0 (uint32_t a);\n" +
"\n"
@parser.treat_inlines = :include
@parser.inline_function_patterns = ['static __inline__ __attribute__ \(\(always_inline\)\)', 'static __inline__']
assert_equal(expected, @parser.transform_inline_functions(source))
end
it "Transform inline functions limits deleting user macro to actual line/word" do
source =
"#if defined (FORCE_INLINE)\n" +
"#define MY_LIBRARY_INLINE static __inline__ __attribute__ ((always_inline))\n" +
"#else\n" +
"#define MY_LIBRARY_INLINE\n" +
"#endif\n" +
"#define INLINE static __inline__ __attribute__ ((always_inline))\n" +
"#define INLINE_TWO \\\nstatic\\\ninline\n" +
"INLINE uint16_t _somefunc (uint32_t a)\n" +
"{\n" +
" return _someotherfunc (a);\n" +
"}\n" +
"static __inline__ uint16_t _somefunc_0 (uint32_t a)\n" +
"{\n" +
" return (uint16_t) a;\n" +
"}\n" +
"static __inline__ __attribute__ \(\(always_inline\)\) uint16_t _somefunc_1 (uint32_t a)\n" +
"{\n" +
" return (uint16_t) a;\n" +
"}\n" +
"INLINE_TWO uint16_t _somefunc_2(uint32_t a)\n" +
"{\n" +
" return (uint16_t) a;\n" +
"}\n" +
"#define INLINE_THREE \\\nstatic\\\ninline"
expected =
"#if defined (FORCE_INLINE)\n" +
"#else\n" +
"#endif\n" +
"uint16_t _somefunc (uint32_t a);\n" +
"uint16_t _somefunc_0 (uint32_t a);\n" +
"uint16_t _somefunc_1 (uint32_t a);\n" +
"uint16_t _somefunc_2(uint32_t a);\n"
@parser.treat_inlines = :include
@parser.inline_function_patterns = ['MY_LIBRARY_INLINE', 'INLINE_THREE', 'INLINE_TWO', 'INLINE', 'static __inline__ __attribute__ \(\(always_inline\)\)', 'static __inline__']
assert_equal(expected, @parser.transform_inline_functions(source))
end
end
+1 -1