Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed including standard libraries from mock sources. #454

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Gemfile.lock
*.swp
examples/make_example/build
examples/temp_sensor/build
*~
35 changes: 34 additions & 1 deletion docs/CMock_Summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ replied with a version that is older than 2.0.0. Go ahead. We'll wait.
Once you have Ruby, you have three options:

* Clone the latest [CMock repo on github](https://github.com/ThrowTheSwitch/CMock/)
(This includes updating the submodules: `git submodules update --recursive`)
* Download the latest [CMock zip from github](https://github.com/ThrowTheSwitch/CMock/)
* Install Ceedling (which has it built in!) through your commandline using `gem install ceedling`.

Expand Down Expand Up @@ -135,7 +136,7 @@ that resembles a pointer or array, it breaks the argument into TWO arguments.
The first is the original pointer. The second specify the number of elements
it is to verify of that array. If you specify 1, it'll check one object. If 2,
it'll assume your pointer is pointing at the first of two elements in an array.
If you specify zero elements and `UNITY_COMPARE_PTRS_ON_ZERO_ARRAY` is defined,
If you specify zero elements and `UNITY_COMPARE_PTRS_ON_ZERO_ARRAY` is defined,
then this assertion can also be used to directly compare the pointers to verify
that they are pointing to the same memory address.

Expand Down Expand Up @@ -385,6 +386,38 @@ from the defaults. We've tried to specify what the defaults are below.
* **note:** this option will reinsert these attributes onto the mock's calls.
If that isn't what you are looking for, check out :strippables.

* `:process_cpp_attributes`:
Allows the parsing and processing of C++ attribute syntax `[[...]]`.

* defaults: false

* `:process_gcc_attributes`:
Allows the parsing and processing of GNU gcc attribute syntax
`__attribute__ ((...))`.
When setting it to true, mind removing `__attribute__` from the
`:strippables`` option.
Those attributes are matched both before and after the function name.

* defaults: false

* `:noreturn_attributes`:
To allow processing of noreturn attributes, list in
`:noreturn_attributes` the keywords used as noreturn attributes.
(Since such attributes may hide behind macros, you may need to list
the macros too).
> :noreturn_attributes => ['_Noreturn', 'noreturn', '__noreturn__']
Note: when a keyword is listed in this option, it's available for
both the C syntax (keyword standing alone), the C++ syntax (keyword
in a `[[...]]` syntax, and the GNU gcc syntax (keyword in a
`__attribute__((...))` syntax)
When a noreturn attribute is matched, the returned function
dictionary will contain a :noreturn => true` entry, and the cmock
code generator will then avoid returning from the mocked function,
but instead will use the `TEST_DO_NOT_RETURN()` macro.

* defaults: []


* `:c_calling_conventions`:
Similarly, CMock may need to understand which C calling conventions
might show up in your codebase. If it encounters something it doesn't
Expand Down
160 changes: 160 additions & 0 deletions lib/CLexer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#
# This is a simple lexer for the C programming language.
# MIT license. (c) 2023 Pascal Bourguignon
#

class CLexer
#
# CLexer is a simple C lexer. It is used to tokenize a C source file.
#
# Usage:
# lexer = CLexer.new(pre_processed_c_source)
# tokens = lexer.tokenize
#
# The tokenize method returns an array of tokens.

KEYWORDS = %w[auto break case char const continue default do double else enum
extern float for goto if int long register return short signed
sizeof static struct switch typedef union unsigned void volatile while].freeze

STAR = :mul_op
ADDRESS = :logical_and_op

OPERATOR_SYMBOLS = {

'...' => :ellipsis,
'->*' => :ptr_mem_op,
'>>=' => :right_assign,
'<<=' => :left_assign,

'==' => :eq,
'!=' => :ne,
'<=' => :le,
'>=' => :ge,
'>>' => :right_op,
'<<' => :left_op,
'+=' => :add_assign,
'-=' => :sub_assign,
'*=' => :mul_assign,
'/=' => :div_assign,
'%=' => :mod_assign,
'&=' => :and_assign,
'^=' => :xor_assign,
'|=' => :or_assign,
'->' => :ptr_op,
'&&' => :and_op,
'||' => :or_op,
'++' => :increment,
'--' => :decrement,

'<:' => :open_bracket,
':>' => :close_bracket,
'<%' => :open_brace,
'%>' => :close_brace,

'!' => :logical_not_op,
'%' => :mod_op,
'&' => :logical_and_op,
'(' => :open_paren,
')' => :close_paren,
'*' => :mul_op,
'+' => :add_op,
',' => :comma,
'-' => :sub_op,
'.' => :dot,
'/' => :div_op,
':' => :colon,
';' => :semicolon,
'<' => :lt,
'=' => :assign,
'>' => :gt,
'?' => :question,
'[' => :open_bracket,
']' => :close_bracket,
'^' => :logical_xor_op,
'{' => :open_brace,
'|' => :logical_or_op,
'}' => :close_brace,
'~' => :bitwise_not_op

}.freeze

OPERATOR_REGEX = Regexp.new('\A(' + OPERATOR_SYMBOLS.keys.map { |op| Regexp.escape(op) }.join('|') + ')')
OPERATOR_SYMS = OPERATOR_SYMBOLS.values.freeze
KEYWORDS_SYMS = KEYWORDS.map(&:to_sym).freeze

def initialize(input)
@input = input
@tokens = []
end

def tokenize
while @input.size.positive?
case @input
when /\A[[:space:]]+/m
@input = $'
when /\A\/\/[^\n]*/
@input = $'
when /\A\/\*/
consume_multiline_comment
when /\A[_a-zA-Z][_a-zA-Z0-9]*/
identifier_or_keyword = $&
@input = $'
if KEYWORDS.include?(identifier_or_keyword)
@tokens << identifier_or_keyword.to_sym
else
@tokens << [:identifier, identifier_or_keyword]
end
when /\A\d+\.\d*([eE][+-]?\d+)?[fFlL]?|\.\d+([eE][+-]?\d+)?[fFlL]?|\d+[eE][+-]?\d+[fFlL]?/
float_constant = $&
@input = $'
@tokens << [:float_literal, float_constant]
when /\A\d+/
integer_constant = $&
@input = $'
@tokens << [:integer_literal, integer_constant]
when /\A0[xX][0-9a-fA-F]+/
hex_constant = $&
@input = $'
@tokens << [:hex_literal, hex_constant]
when /\A'((\\.|[^\\'])*)'/
char_literal = $&
@input = $'
@tokens << [:char_literal, char_literal]
when /\A"((\\.|[^\\"])*)"/
string_literal = $&
@input = $'
@tokens << [:string_literal, string_literal]
when OPERATOR_REGEX
operator = $&
@input = $'
@tokens << OPERATOR_SYMBOLS[operator]
else
raise "Unexpected character: #{@input[0].inspect}"
end
end

@tokens
end

private

def consume_multiline_comment
while @input.size.positive?
case @input
when /\A\*\//
@input = $'
break
when /\A./m
@input = $'
end
end
end
end

def example
input = File.read('tee.c')
lexer = CLexer.new(input)
tokens = lexer.tokenize
puts tokens.inspect
end
6 changes: 2 additions & 4 deletions lib/cmock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,12 @@ def setup_skeletons(files)
def generate_mock(src, folder)
name = File.basename(src, '.*')
ext = File.extname(src)
puts "Creating mock for #{name}..." unless @silent
@cm_generator.create_mock(name, @cm_parser.parse(name, File.read(src)), ext, folder)
@cm_generator.create_mock(name, @cm_parser.parse(name, File.read(src), src), ext, folder, src)
end

def generate_skeleton(src)
name = File.basename(src, '.*')
puts "Creating skeleton for #{name}..." unless @silent
@cm_generator.create_skeleton(name, @cm_parser.parse(name, File.read(src)))
@cm_generator.create_skeleton(name, @cm_parser.parse(name, File.read(src), src), src)
end
end

Expand Down
4 changes: 4 additions & 0 deletions lib/cmock_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class CMockConfig
:subdir => nil,
:plugins => [],
:strippables => ['(?:__attribute__\s*\([ (]*.*?[ )]*\)+)'],
:process_gcc_attributes => false, # __attribute__((...)) ; also remove it from strippables.
:process_cpp_attributes => false, # [[ ... ]]
:noreturn_attributes => [], # simple keyword, before the function name
:attributes => %w[__ramfunc __irq __fiq register extern],
:c_calling_conventions => %w[__stdcall __cdecl __fastcall],
:enforce_strict_ordering => false,
Expand All @@ -32,6 +35,7 @@ class CMockConfig
:treat_inlines => :exclude, # the options being :include or :exclude
:callback_include_count => true,
:callback_after_arg_check => false,
:callback_kind => "*",
:includes => nil,
:includes_h_pre_orig_header => nil,
:includes_h_post_orig_header => nil,
Expand Down
2 changes: 2 additions & 0 deletions lib/cmock_file_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def create_file(filename, subdir)

full_file_name_temp = "#{@config.mock_path}/#{subdir + '/' if subdir}#{filename}.new"
full_file_name_done = "#{@config.mock_path}/#{subdir + '/' if subdir}#{filename}"
puts "Creating #{full_file_name_done.inspect}" unless @config.verbosity < 2

File.open(full_file_name_temp, 'w') do |file|
yield(file, filename)
end
Expand Down
52 changes: 40 additions & 12 deletions lib/cmock_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def initialize(config, file_writer, utils, plugins)
@fail_on_unexpected_calls = @config.fail_on_unexpected_calls
@exclude_setjmp_h = @config.exclude_setjmp_h
@subdir = @config.subdir

@silent = (@config.verbosity < 2)
@includes_h_pre_orig_header = ((@config.includes || []) + (@config.includes_h_pre_orig_header || [])).uniq.map { |h| h =~ /</ ? h : "\"#{h}\"" }
@includes_h_post_orig_header = (@config.includes_h_post_orig_header || []).map { |h| h =~ /</ ? h : "\"#{h}\"" }
@includes_c_pre_header = (@config.includes_c_pre_header || []).map { |h| h =~ /</ ? h : "\"#{h}\"" }
Expand All @@ -44,7 +44,9 @@ def initialize(config, file_writer, utils, plugins)
end
end

def create_mock(module_name, parsed_stuff, module_ext = nil, folder = nil)
def create_mock(module_name, parsed_stuff, module_ext = nil, folder = nil, src = nil)
$stderr.puts "Creating mock for #{src ? src : module_name}..." unless @silent

# determine the name for our new mock
mock_name = @prefix + module_name + @suffix

Expand All @@ -68,15 +70,18 @@ def create_mock(module_name, parsed_stuff, module_ext = nil, folder = nil)
:clean_name => TypeSanitizer.sanitize_c_identifier(mock_name),
:folder => mock_folder,
:parsed_stuff => parsed_stuff,
:skeleton => false
:skeleton => false,
:source => src
}

create_mock_subdir(mock_project)
create_mock_header_file(mock_project)
create_mock_source_file(mock_project)
end

def create_skeleton(module_name, parsed_stuff)
def create_skeleton(module_name, parsed_stuff, src = nil)
$stderr.puts "Creating skeleton for #{src ? src : module_name}..." unless @silent

mock_project = {
:module_name => module_name,
:module_ext => '.h',
Expand All @@ -89,6 +94,30 @@ def create_skeleton(module_name, parsed_stuff)

private if $ThisIsOnlyATest.nil? ##############################

def is_noreturn?(function)
function[:noreturn]
end

def generate_return(function)
if is_noreturn?(function)
return " TEST_DO_NOT_RETURN();\n"
elsif function[:return][:void?]
return " return;\n"
else
return " return cmock_call_instance->ReturnVal;\n"
end
end

def generate_template_return(function)
if is_noreturn?(function)
return " TEST_DO_NOT_RETURN();\n"
elsif function[:return][:void?]
return " return;\n"
else
return " return (#{(function[:return][:type])})0;\n"
end
end

def create_mock_subdir(mock_project)
@file_writer.create_subdir(mock_project[:folder])
end
Expand All @@ -100,11 +129,14 @@ def create_using_statement(file, function)
def create_mock_header_file(mock_project)
if @include_inline == :include
@file_writer.create_file(mock_project[:module_name] + (mock_project[:module_ext]), mock_project[:folder]) do |file, _filename|
file << "/* Source File: #{mock_project[:source]} */\n"
file << "/* Normalized source (without inlines). */\n"
file << mock_project[:parsed_stuff][:normalized_source]
end
end

@file_writer.create_file(mock_project[:mock_name] + mock_project[:module_ext], mock_project[:folder]) do |file, filename|
file << "/* Source File: #{mock_project[:source]} */\n"
create_mock_header_header(file, filename, mock_project)
create_mock_header_service_call_declarations(file, mock_project)
create_typedefs(file, mock_project)
Expand All @@ -118,6 +150,7 @@ def create_mock_header_file(mock_project)

def create_mock_source_file(mock_project)
@file_writer.create_file(mock_project[:mock_name] + '.c', mock_project[:folder]) do |file, filename|
file << "/* Source File: #{mock_project[:source]} */\n"
create_source_header_section(file, filename, mock_project)
create_instance_structure(file, mock_project)
create_extern_declarations(file)
Expand Down Expand Up @@ -209,11 +242,6 @@ def create_mock_header_footer(header)
def create_source_header_section(file, filename, mock_project)
header_file = (mock_project[:folder] || '') + filename.gsub('.c', mock_project[:module_ext])
file << "/* AUTOGENERATED FILE. DO NOT EDIT. */\n" unless mock_project[:parsed_stuff][:functions].empty?
file << "#include <string.h>\n"
file << "#include <stdlib.h>\n"
unless @exclude_setjmp_h
file << "#include <setjmp.h>\n"
end
file << "#include \"cmock.h\"\n"
@includes_c_pre_header.each { |inc| file << "#include #{inc}\n" }
file << "#include \"#{header_file}\"\n"
Expand Down Expand Up @@ -283,7 +311,7 @@ def create_mock_init_function(file, mock_project)
def create_mock_destroy_function(file, mock_project)
file << "void #{mock_project[:clean_name]}_Destroy(void)\n{\n"
file << " CMock_Guts_MemFreeAll();\n"
file << " memset(&Mock, 0, sizeof(Mock));\n"
file << " CMock_memset(&Mock, 0, sizeof(Mock));\n"
file << mock_project[:parsed_stuff][:functions].collect { |function| @plugins.run(:mock_destroy, function) }.join

unless @fail_on_unexpected_calls
Expand Down Expand Up @@ -342,7 +370,7 @@ def create_mock_implementation(file, function)
end
file << @plugins.run(:mock_implementation, function)
file << " UNITY_CLR_DETAILS();\n"
file << " return cmock_call_instance->ReturnVal;\n" unless function[:return][:void?]
file << generate_return(function);
file << "}\n"

# Close any namespace(s) opened above
Expand Down Expand Up @@ -374,7 +402,7 @@ def create_function_skeleton(file, function, existing)
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 << generate_template_return(function);
file << "}\n\n"
end
end
Loading
Loading