Merge pull request #535 from ThrowTheSwitch/refactor/actions

Implement broader testing across platforms.
This commit is contained in:
Mark VanderVoord
2026-07-01 14:14:50 -04:00
committed by GitHub
8 changed files with 232 additions and 46 deletions
+144 -25
View File
@@ -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: Install Multilib
run: |
sudo apt-get update -qq
sudo apt-get install --assume-yes --quiet gcc-multilib
# Checks out repository under $GITHUB_WORKSPACE
- 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 }}
# 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
# Run Tests
- name: Run All Unit Tests
- name: Run Ruby Unit Tests
env:
NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }}
run: |
cd test && rake ci
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
# 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@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
- 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
- name: Valgrind Check - System Tests
run: |
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
+4 -1
View File
@@ -1 +1,4 @@
source "http://rubygems.org/"
source "https://rubygems.org/"
gem 'rspec'
gem 'rubocop', '1.57.2'
+23 -3
View File
@@ -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.
@@ -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:
----------------
@@ -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
@@ -80,12 +80,11 @@ class CMockGeneratorPluginReturnThruPtr
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
@@ -136,11 +135,10 @@ class CMockGeneratorPluginReturnThruPtr
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
+8 -10
View File
@@ -15,7 +15,7 @@ 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
@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
@@ -118,15 +118,13 @@ class CMockHeaderParser
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
@@ -311,7 +309,7 @@ class CMockHeaderParser
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
+48
View File
@@ -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
+1 -1
View File
@@ -80,7 +80,7 @@ module RakefileHelpers
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
@@ -253,14 +253,14 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh
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"