Skip to content
Merged
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
161 changes: 140 additions & 21 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,161 @@ on:
branches: [ master ]

jobs:
# Job: Unit test suite
unit-tests:
name: "Unit Tests"
runs-on: ubuntu-latest
# Job: Ruby unit tests across all supported Ruby versions and OSes.
# These are fast (no C compilation) and verify the generator logic is Ruby-version-portable.
ruby-tests:
name: "Ruby Tests (${{ matrix.os }}, Ruby ${{ matrix.ruby }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
ruby: ['3.0', '3.1', '3.2', '3.3']
exclude:
# create sparse matrix to avoid pointless duplication
- os: macos-latest
ruby: '3.0'
- os: macos-latest
ruby: '3.1'
- os: macos-latest
ruby: '3.2'
- os: windows-latest
ruby: '3.0'
- os: windows-latest
ruby: '3.1'
- os: windows-latest
ruby: '3.2'
steps:
# Install Multilib
- name: Checkout Latest Repo
uses: actions/checkout@v4
with:
submodules: recursive

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}

- name: Install Ruby Dependencies
run: |
gem install bundler
bundle install

- name: Run Ruby Unit Tests
env:
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
run: |
cd test && rake test:unit

- name: Run Style Check
env:
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
run: |
cd test && rake style:check

# Job: C compilation and system tests — only needs to run on one Ruby version per OS,
# since the generated C code and runtime behavior don't vary with the Ruby version.
c-tests:
name: "C Tests (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
# Install Multilib (Linux only)
- name: Install Multilib
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update -qq
sudo apt-get install --assume-yes --quiet gcc-multilib

# Checks out repository under $GITHUB_WORKSPACE
# Add MinGW GCC to PATH (Windows only — MSYS2 is pre-installed on the runner)
- name: Add GCC to PATH
if: matrix.os == 'windows-latest'
run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Checkout Latest Repo
uses: actions/checkout@v2
with:
uses: actions/checkout@v4
with:
submodules: recursive

# Setup Ruby Testing Tools to do tests on multiple ruby version
- name: Setup Ruby Testing Tools
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
ruby-version: '3.3'

# Install Ruby Testing Tools
- name: Setup Ruby Testing Tools
- name: Install Ruby Dependencies
run: |
sudo gem install rspec
sudo gem install rubocop -v 1.57.2
sudo gem install bundler
bundle update
bundle install
gem install bundler
bundle install

- name: Run C Unit Tests
env:
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
run: cd test && rake test:c

- name: Run System Tests
env:
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
run: cd test && rake test:system

- name: Run Examples
env:
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
run: cd test && rake test:examples

# Job: Valgrind memory-leak check (Linux/gcc_64 only, latest Ruby)
valgrind:
name: "Valgrind Memory Check"
runs-on: ubuntu-latest
steps:
- name: Install Dependencies
run: |
sudo apt-get update -qq
sudo apt-get install --assume-yes --quiet gcc-multilib valgrind

- name: Checkout Latest Repo
uses: actions/checkout@v4
with:
submodules: recursive

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'

- name: Install Ruby Dependencies
run: |
gem install bundler
bundle install

# Build and run C unit tests, then re-run the executable under valgrind.
# test:system clobbers the build directory, so check TestCMockC before that happens.
- name: Build and Run C Unit Tests
run: cd test && rake config[gcc_64_valgrind] test:c

- name: Valgrind Check - C Unit Tests
run: |
valgrind --leak-check=full --track-origins=yes --error-exitcode=1 \
test/system/build/TestCMockC.exe

# Build and run system tests, then re-run each executable under valgrind.
- name: Build and Run System Tests
run: cd test && rake config[gcc_64_valgrind] test:system

# Run Tests
- name: Run All Unit Tests
- name: Valgrind Check - System Tests
run: |
cd test && rake ci
failed=0
for exe in test/system/build/test_*.exe; do
echo "Checking: $exe"
# Use exit code 42 to distinguish valgrind errors from Unity test failures.
# Some executables intentionally contain tests expected to fail (testing CMock's
# error-handling), so Unity exits non-zero. The `|| exit_code=$?` prevents bash's
# set -e from aborting the script on Unity's non-zero exit, while still capturing
# valgrind's own error exit code (42) separately.
exit_code=0
valgrind --leak-check=full --track-origins=yes --error-exitcode=42 "$exe" || exit_code=$?
[ $exit_code -eq 42 ] && failed=1
done
exit $failed
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
source "http://rubygems.org/"
source "https://rubygems.org/"

gem 'rspec'
gem 'rubocop', '1.57.2'
26 changes: 23 additions & 3 deletions docs/CMock_Summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ care how many times it was called, right?
StopIgnore:
-------

Maybe you want to ignore a particular function for part of a test but dont want to
Maybe you want to ignore a particular function for part of a test but don't want to
ignore it later on. In that case, you want to use StopIgnore which will cancel the
previously called Ignore or IgnoreAndReturn requiring you to Expect or otherwise
handle the call to a function.
Expand All @@ -199,6 +199,25 @@ handle the call to a function.
* `retval func(void)` => `void func_StopIgnore(void)`
* `retval func(params)` => `void func_StopIgnore(void)`

It's important to note that the effect of this function is immediate and applies to
this function's stack of expectations. So the following will work as intended:

```
Blah_Ignore();
funcThatMightCallBlahButWeDoNotCare();
Blah_StopIgnore();
funcThatWeWantToMakeSureDoesNotCallBlah();
```

But this is NOT going to work, because StopIgnore immediately cancels ignore:

```
Blah_Ignore();
Blah_StopIgnore();
funcThatMightCallBlahButWeDoNotCare();
funcThatWeWantToMakeSureDoesNotCallBlah();
```

IgnoreStateless:
----------------

Expand Down Expand Up @@ -951,8 +970,9 @@ that exposes them as virtual methods and modify your code to inject mocks at
run-time... but there is another way!

Simply use CMock to mock the static member methods and a C++ mocking framework
to handle the virtual methods. (Yes, you can mix mocks from CMock and a C++
mocking framework together in the same test!)
to handle the virtual methods. CMock does NOT mock non-static members. For those,
you'll need an actual C++ mocking framework. (Yes, you can mix mocks from CMock
and a C++ mocking framework together in the same test!)

Keep in mind that since C++ mocking frameworks often link the real object to the
unit test too, we need to resolve multiple definition errors with something like
Expand Down
6 changes: 2 additions & 4 deletions lib/cmock_generator_plugin_return_thru_ptr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,11 @@ def mock_precheck_return_thru_ptr(function)
arg_name = arg[:name]
next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?])

dest_cast = arg[:volatile?] ? '(void*)(CMOCK_MEM_PTR_AS_INT)' : '(void*)'
lines << " if (Mock.#{function[:name]}_IgnoreBool && cmock_call_instance != NULL &&\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n"
lines << " {\n"
lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n"
lines << " CMOCK_MEMCPY(#{dest_cast}#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n"
lines << " }\n"
end
Expand Down Expand Up @@ -136,11 +135,10 @@ def mock_implementation(function)
arg_name = arg[:name]
next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?])

dest_cast = arg[:volatile?] ? '(void*)(CMOCK_MEM_PTR_AS_INT)' : '(void*)'
lines << " if (cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n"
lines << " {\n"
lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n"
lines << " CMOCK_MEMCPY(#{dest_cast}#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n"
lines << " }\n"
end
Expand Down
18 changes: 8 additions & 10 deletions lib/cmock_header_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize(cfg)
@c_calling_conventions = cfg.c_calling_conventions.uniq
@treat_as_array = cfg.treat_as_array
@treat_as_void = (['void'] + cfg.treat_as_void).uniq
@function_declaration_parse_base_match = '([\w\s\*\(\),\[\]]*?\w[\w\s\*\(\),\[\]]*?)\(([\w\s\*\(\),\.\[\]+\-\/]*)\)'
@function_declaration_parse_base_match = '([^(]*\w)\s*\(([\w\s\*\(\),\.\[\]+\-\/]*)\)'
@declaration_parse_matcher = /#{@function_declaration_parse_base_match}$/m
@standards = (%w[int short char long unsigned signed] + cfg.treat_as.keys).uniq
@array_size_name = cfg.array_size_name
Expand Down Expand Up @@ -118,15 +118,13 @@ def remove_comments_from_source(source)

def remove_nested_pairs_of_braces(source)
# remove nested pairs of braces because no function declarations will be inside of them (leave outer pair for function definition detection)
if RUBY_VERSION.split('.')[0].to_i > 1
# we assign a string first because (no joke) if Ruby 1.9.3 sees this line as a regex, it will crash.
r = '\\{([^\\{\\}]*|\\g<0>)*\\}'
source.gsub!(/#{r}/m, '{ }')
else
while source.gsub!(/\{[^{}]*\{[^{}]*\}[^{}]*\}/m, '{ }')
end
# Collapse innermost brace pairs first using a brace-free sentinel (\x00), working
# outward until no balanced pairs remain. This avoids the catastrophic backtracking
# of the recursive regex \{([^\{\}]*|\g<0>)*\} on Ruby < 3.2 while preserving
# identical semantics: every balanced brace structure is collapsed to '{ }'.
while source.gsub!(/\{[^{}]*\}/m, "\x00")
end

source.gsub!("\x00", '{ }')
source
end

Expand Down Expand Up @@ -311,7 +309,7 @@ def import_source(source, parse_project, cpp = false)
source.gsub!(/\b(?:#{@ct_assert_patterns.join('|')})\s*\([^;]*\)/, '') unless @ct_assert_patterns.empty?
# strip any remaining WORD(...==...) etc. -- calls containing comparison operators cannot be C function prototypes
# must run before default-value removal, which would corrupt "!= 0" into "!" by removing "= 0"
source.gsub!(/\b\w+\s*\((?:[^()!=<>]*(?:\([^()]*\))*)*(?:==|!=|<=|>=)[^;]*\)/, '')
source.gsub!(/\b\w+\s*\((?:[^()!=<>]|\([^()]*\))*(?:==|!=|<=|>=)[^;]*\)/, '')

source.gsub!(/\s*=\s*['"a-zA-Z0-9_.]+\s*/, '') # remove default value statements from argument lists

Expand Down
48 changes: 48 additions & 0 deletions test/gcc_64_valgrind.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# =========================================================================
# CMock - Automatic Mock Generation for C
# ThrowTheSwitch.org
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================

# gcc_64 with debug symbols enabled for meaningful valgrind output.
# Used by the CI valgrind job to check for memory leaks and errors.

---
:tools:
:test_compiler:
:name: compiler
:executable: gcc
:arguments:
- "-c"
- "-m64"
- "-g"
- "-Wall"
- "-Wno-address"
- "-std=c99"
- "-pedantic"
- '-I"${5}"'
- "-D${6}"
- "${1}"
- "-o ${2}"
:test_linker:
:name: linker
:executable: gcc
:arguments:
- "${1}"
- "-lm"
- "-m64"
- "-o ${2}"
:extension:
:object: ".o"
:executable: ".exe"
:defines:
:test:
- UNITY_EXCLUDE_STDINT_H
- UNITY_EXCLUDE_LIMITS_H
- UNITY_INCLUDE_DOUBLE
- UNITY_SUPPORT_TEST_CASES
- UNITY_SUPPORT_64
- UNITY_INT_WIDTH=32
- UNITY_LONG_WIDTH=64
- UNITY_POINTER_WIDTH=64
2 changes: 1 addition & 1 deletion test/rakefile_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def load_configuration(config_file, cmock_overlay = nil)
raise "Cannot find Config File #{config_target}"
end

$colour_output = $proj[:project][:colour]
$colour_output = $proj[:project][:colour] && !ENV['NO_COLOR']
end

def configure_clean
Expand Down
4 changes: 2 additions & 2 deletions test/unit/cmock_generator_plugin_return_thru_ptr_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -253,14 +253,14 @@ def volatile_ptr_func_expect
assert_equal(expected, returned)
end

it "uses (void*)(CMOCK_MEM_PTR_AS_INT) cast in mock_implementation for volatile pointer arg" do
it "uses (void*) cast in mock_implementation for volatile pointer arg" do
volatile_ptr_func_expect()

expected =
" if (cmock_call_instance->ReturnThruPtr_foo_handle_Used)\n" +
" {\n" +
" UNITY_TEST_ASSERT_NOT_NULL(foo_handle, cmock_line, CMockStringPtrIsNULL);\n" +
" CMOCK_MEMCPY((void*)(CMOCK_MEM_PTR_AS_INT)foo_handle, (const void*)cmock_call_instance->ReturnThruPtr_foo_handle_Val,\n" +
" CMOCK_MEMCPY((void*)foo_handle, (const void*)cmock_call_instance->ReturnThruPtr_foo_handle_Val,\n" +
" cmock_call_instance->ReturnThruPtr_foo_handle_Size);\n" +
" }\n"

Expand Down
Loading