Skip to content

Commit 6560abf

Browse files
authored
Merge pull request #535 from ThrowTheSwitch/refactor/actions
Implement broader testing across platforms.
2 parents 8c3a336 + a737db3 commit 6560abf

8 files changed

Lines changed: 228 additions & 42 deletions

.github/workflows/main.yml

Lines changed: 140 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,161 @@ on:
1111
branches: [ master ]
1212

1313
jobs:
14-
# Job: Unit test suite
15-
unit-tests:
16-
name: "Unit Tests"
17-
runs-on: ubuntu-latest
14+
# Job: Ruby unit tests across all supported Ruby versions and OSes.
15+
# These are fast (no C compilation) and verify the generator logic is Ruby-version-portable.
16+
ruby-tests:
17+
name: "Ruby Tests (${{ matrix.os }}, Ruby ${{ matrix.ruby }})"
18+
runs-on: ${{ matrix.os }}
1819
strategy:
20+
fail-fast: false
1921
matrix:
22+
os: [ubuntu-latest, windows-latest, macos-latest]
2023
ruby: ['3.0', '3.1', '3.2', '3.3']
24+
exclude:
25+
# create sparse matrix to avoid pointless duplication
26+
- os: macos-latest
27+
ruby: '3.0'
28+
- os: macos-latest
29+
ruby: '3.1'
30+
- os: macos-latest
31+
ruby: '3.2'
32+
- os: windows-latest
33+
ruby: '3.0'
34+
- os: windows-latest
35+
ruby: '3.1'
36+
- os: windows-latest
37+
ruby: '3.2'
2138
steps:
22-
# Install Multilib
39+
- name: Checkout Latest Repo
40+
uses: actions/checkout@v4
41+
with:
42+
submodules: recursive
43+
44+
- name: Setup Ruby
45+
uses: ruby/setup-ruby@v1
46+
with:
47+
ruby-version: ${{ matrix.ruby }}
48+
49+
- name: Install Ruby Dependencies
50+
run: |
51+
gem install bundler
52+
bundle install
53+
54+
- name: Run Ruby Unit Tests
55+
env:
56+
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
57+
run: |
58+
cd test && rake test:unit
59+
60+
- name: Run Style Check
61+
env:
62+
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
63+
run: |
64+
cd test && rake style:check
65+
66+
# Job: C compilation and system tests — only needs to run on one Ruby version per OS,
67+
# since the generated C code and runtime behavior don't vary with the Ruby version.
68+
c-tests:
69+
name: "C Tests (${{ matrix.os }})"
70+
runs-on: ${{ matrix.os }}
71+
strategy:
72+
fail-fast: false
73+
matrix:
74+
os: [ubuntu-latest, windows-latest, macos-latest]
75+
steps:
76+
# Install Multilib (Linux only)
2377
- name: Install Multilib
78+
if: matrix.os == 'ubuntu-latest'
2479
run: |
2580
sudo apt-get update -qq
2681
sudo apt-get install --assume-yes --quiet gcc-multilib
2782
28-
# Checks out repository under $GITHUB_WORKSPACE
83+
# Add MinGW GCC to PATH (Windows only — MSYS2 is pre-installed on the runner)
84+
- name: Add GCC to PATH
85+
if: matrix.os == 'windows-latest'
86+
run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
87+
2988
- name: Checkout Latest Repo
30-
uses: actions/checkout@v2
31-
with:
89+
uses: actions/checkout@v4
90+
with:
3291
submodules: recursive
3392

34-
# Setup Ruby Testing Tools to do tests on multiple ruby version
35-
- name: Setup Ruby Testing Tools
93+
- name: Setup Ruby
3694
uses: ruby/setup-ruby@v1
3795
with:
38-
ruby-version: ${{ matrix.ruby }}
96+
ruby-version: '3.3'
3997

40-
# Install Ruby Testing Tools
41-
- name: Setup Ruby Testing Tools
98+
- name: Install Ruby Dependencies
4299
run: |
43-
sudo gem install rspec
44-
sudo gem install rubocop -v 1.57.2
45-
sudo gem install bundler
46-
bundle update
47-
bundle install
100+
gem install bundler
101+
bundle install
102+
103+
- name: Run C Unit Tests
104+
env:
105+
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
106+
run: cd test && rake test:c
107+
108+
- name: Run System Tests
109+
env:
110+
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
111+
run: cd test && rake test:system
112+
113+
- name: Run Examples
114+
env:
115+
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
116+
run: cd test && rake test:examples
117+
118+
# Job: Valgrind memory-leak check (Linux/gcc_64 only, latest Ruby)
119+
valgrind:
120+
name: "Valgrind Memory Check"
121+
runs-on: ubuntu-latest
122+
steps:
123+
- name: Install Dependencies
124+
run: |
125+
sudo apt-get update -qq
126+
sudo apt-get install --assume-yes --quiet gcc-multilib valgrind
127+
128+
- name: Checkout Latest Repo
129+
uses: actions/checkout@v4
130+
with:
131+
submodules: recursive
132+
133+
- name: Setup Ruby
134+
uses: ruby/setup-ruby@v1
135+
with:
136+
ruby-version: '3.3'
137+
138+
- name: Install Ruby Dependencies
139+
run: |
140+
gem install bundler
141+
bundle install
142+
143+
# Build and run C unit tests, then re-run the executable under valgrind.
144+
# test:system clobbers the build directory, so check TestCMockC before that happens.
145+
- name: Build and Run C Unit Tests
146+
run: cd test && rake config[gcc_64_valgrind] test:c
147+
148+
- name: Valgrind Check - C Unit Tests
149+
run: |
150+
valgrind --leak-check=full --track-origins=yes --error-exitcode=1 \
151+
test/system/build/TestCMockC.exe
152+
153+
# Build and run system tests, then re-run each executable under valgrind.
154+
- name: Build and Run System Tests
155+
run: cd test && rake config[gcc_64_valgrind] test:system
48156

49-
# Run Tests
50-
- name: Run All Unit Tests
157+
- name: Valgrind Check - System Tests
51158
run: |
52-
cd test && rake ci
159+
failed=0
160+
for exe in test/system/build/test_*.exe; do
161+
echo "Checking: $exe"
162+
# Use exit code 42 to distinguish valgrind errors from Unity test failures.
163+
# Some executables intentionally contain tests expected to fail (testing CMock's
164+
# error-handling), so Unity exits non-zero. The `|| exit_code=$?` prevents bash's
165+
# set -e from aborting the script on Unity's non-zero exit, while still capturing
166+
# valgrind's own error exit code (42) separately.
167+
exit_code=0
168+
valgrind --leak-check=full --track-origins=yes --error-exitcode=42 "$exe" || exit_code=$?
169+
[ $exit_code -eq 42 ] && failed=1
170+
done
171+
exit $failed

Gemfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
source "http://rubygems.org/"
1+
source "https://rubygems.org/"
2+
3+
gem 'rspec'
4+
gem 'rubocop', '1.57.2'

docs/CMock_Summary.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ care how many times it was called, right?
189189
StopIgnore:
190190
-------
191191

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

202+
It's important to note that the effect of this function is immediate and applies to
203+
this function's stack of expectations. So the following will work as intended:
204+
205+
```
206+
Blah_Ignore();
207+
funcThatMightCallBlahButWeDoNotCare();
208+
Blah_StopIgnore();
209+
funcThatWeWantToMakeSureDoesNotCallBlah();
210+
```
211+
212+
But this is NOT going to work, because StopIgnore immediately cancels ignore:
213+
214+
```
215+
Blah_Ignore();
216+
Blah_StopIgnore();
217+
funcThatMightCallBlahButWeDoNotCare();
218+
funcThatWeWantToMakeSureDoesNotCallBlah();
219+
```
220+
202221
IgnoreStateless:
203222
----------------
204223

@@ -951,8 +970,9 @@ that exposes them as virtual methods and modify your code to inject mocks at
951970
run-time... but there is another way!
952971
953972
Simply use CMock to mock the static member methods and a C++ mocking framework
954-
to handle the virtual methods. (Yes, you can mix mocks from CMock and a C++
955-
mocking framework together in the same test!)
973+
to handle the virtual methods. CMock does NOT mock non-static members. For those,
974+
you'll need an actual C++ mocking framework. (Yes, you can mix mocks from CMock
975+
and a C++ mocking framework together in the same test!)
956976

957977
Keep in mind that since C++ mocking frameworks often link the real object to the
958978
unit test too, we need to resolve multiple definition errors with something like

lib/cmock_generator_plugin_return_thru_ptr.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,11 @@ def mock_precheck_return_thru_ptr(function)
8080
arg_name = arg[:name]
8181
next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?])
8282

83-
dest_cast = arg[:volatile?] ? '(void*)(CMOCK_MEM_PTR_AS_INT)' : '(void*)'
8483
lines << " if (Mock.#{function[:name]}_IgnoreBool && cmock_call_instance != NULL &&\n"
8584
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n"
8685
lines << " {\n"
8786
lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n"
88-
lines << " CMOCK_MEMCPY(#{dest_cast}#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
87+
lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
8988
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n"
9089
lines << " }\n"
9190
end
@@ -136,11 +135,10 @@ def mock_implementation(function)
136135
arg_name = arg[:name]
137136
next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?])
138137

139-
dest_cast = arg[:volatile?] ? '(void*)(CMOCK_MEM_PTR_AS_INT)' : '(void*)'
140138
lines << " if (cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n"
141139
lines << " {\n"
142140
lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n"
143-
lines << " CMOCK_MEMCPY(#{dest_cast}#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
141+
lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n"
144142
lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n"
145143
lines << " }\n"
146144
end

lib/cmock_header_parser.rb

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def initialize(cfg)
1515
@c_calling_conventions = cfg.c_calling_conventions.uniq
1616
@treat_as_array = cfg.treat_as_array
1717
@treat_as_void = (['void'] + cfg.treat_as_void).uniq
18-
@function_declaration_parse_base_match = '([\w\s\*\(\),\[\]]*?\w[\w\s\*\(\),\[\]]*?)\(([\w\s\*\(\),\.\[\]+\-\/]*)\)'
18+
@function_declaration_parse_base_match = '([^(]*\w)\s*\(([\w\s\*\(\),\.\[\]+\-\/]*)\)'
1919
@declaration_parse_matcher = /#{@function_declaration_parse_base_match}$/m
2020
@standards = (%w[int short char long unsigned signed] + cfg.treat_as.keys).uniq
2121
@array_size_name = cfg.array_size_name
@@ -118,15 +118,13 @@ def remove_comments_from_source(source)
118118

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

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

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

test/gcc_64_valgrind.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# =========================================================================
2+
# CMock - Automatic Mock Generation for C
3+
# ThrowTheSwitch.org
4+
# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
5+
# SPDX-License-Identifier: MIT
6+
# =========================================================================
7+
8+
# gcc_64 with debug symbols enabled for meaningful valgrind output.
9+
# Used by the CI valgrind job to check for memory leaks and errors.
10+
11+
---
12+
:tools:
13+
:test_compiler:
14+
:name: compiler
15+
:executable: gcc
16+
:arguments:
17+
- "-c"
18+
- "-m64"
19+
- "-g"
20+
- "-Wall"
21+
- "-Wno-address"
22+
- "-std=c99"
23+
- "-pedantic"
24+
- '-I"${5}"'
25+
- "-D${6}"
26+
- "${1}"
27+
- "-o ${2}"
28+
:test_linker:
29+
:name: linker
30+
:executable: gcc
31+
:arguments:
32+
- "${1}"
33+
- "-lm"
34+
- "-m64"
35+
- "-o ${2}"
36+
:extension:
37+
:object: ".o"
38+
:executable: ".exe"
39+
:defines:
40+
:test:
41+
- UNITY_EXCLUDE_STDINT_H
42+
- UNITY_EXCLUDE_LIMITS_H
43+
- UNITY_INCLUDE_DOUBLE
44+
- UNITY_SUPPORT_TEST_CASES
45+
- UNITY_SUPPORT_64
46+
- UNITY_INT_WIDTH=32
47+
- UNITY_LONG_WIDTH=64
48+
- UNITY_POINTER_WIDTH=64

test/rakefile_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def load_configuration(config_file, cmock_overlay = nil)
8080
raise "Cannot find Config File #{config_target}"
8181
end
8282

83-
$colour_output = $proj[:project][:colour]
83+
$colour_output = $proj[:project][:colour] && !ENV['NO_COLOR']
8484
end
8585

8686
def configure_clean

test/unit/cmock_generator_plugin_return_thru_ptr_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,14 @@ def volatile_ptr_func_expect
253253
assert_equal(expected, returned)
254254
end
255255

256-
it "uses (void*)(CMOCK_MEM_PTR_AS_INT) cast in mock_implementation for volatile pointer arg" do
256+
it "uses (void*) cast in mock_implementation for volatile pointer arg" do
257257
volatile_ptr_func_expect()
258258

259259
expected =
260260
" if (cmock_call_instance->ReturnThruPtr_foo_handle_Used)\n" +
261261
" {\n" +
262262
" UNITY_TEST_ASSERT_NOT_NULL(foo_handle, cmock_line, CMockStringPtrIsNULL);\n" +
263-
" CMOCK_MEMCPY((void*)(CMOCK_MEM_PTR_AS_INT)foo_handle, (const void*)cmock_call_instance->ReturnThruPtr_foo_handle_Val,\n" +
263+
" CMOCK_MEMCPY((void*)foo_handle, (const void*)cmock_call_instance->ReturnThruPtr_foo_handle_Val,\n" +
264264
" cmock_call_instance->ReturnThruPtr_foo_handle_Size);\n" +
265265
" }\n"
266266

0 commit comments

Comments
 (0)