ceedling 1.1.2 β 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/GIT_COMMIT_SHA +1 -1
- data/README.md +1 -1
- data/assets/test_example_file_crash_sigsegv_with_param.c +32 -0
- data/bin/actions_wrapper.rb +7 -6
- data/docs/Changelog.md +8 -0
- data/lib/ceedling/config/configurator.rb +7 -1
- data/lib/ceedling/defaults.rb +8 -2
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +136 -68
- data/lib/ceedling/generators/generator_test_runner.rb +20 -2
- data/lib/version.rb +1 -1
- data/plugins/gcov/lib/gcov.rb +2 -2
- data/site-local/sitemap.xml.gz +0 -0
- data/spec/support/system/system_context.rb +15 -0
- data/spec/system/deployment_as_gem_spec.rb +2 -0
- data/spec/system/deployment_as_vendor_spec.rb +2 -0
- data/spec/system/support/common_test_cases.rb +90 -0
- data/spec/units/configurator_spec.rb +45 -0
- data/spec/units/generators/generator_test_results_backtrace_spec.rb +49 -5
- data/spec/units/generators/generator_test_runner_spec.rb +26 -2
- data/spec/units/test_context_extractor_spec.rb +3 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8aabde4baeef95fad89e87cf3bd6226ffa3f10df9e54708270c82f237f3efe49
|
|
4
|
+
data.tar.gz: '08693e70f80deab8cb70f11677291f368a159df04b35f7a8d9fec8025522063a'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab5f32d7896199850b29f6328c9e3fbaea63c0f5bf3dff665434ee0bd488992f971b18afd1bcf194e5c6f2ca66636cd72e38ab26fed3e74904cb374094f9c2b7
|
|
7
|
+
data.tar.gz: fab7cf5fe911d2dbc24bdb16add93551bfe8d9b7a668b888735ccfe199d04431707d5e01ec2d5ce345ee518db9b8f34376e16b92788ba1780e2ed20b8a32832c
|
data/GIT_COMMIT_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
049a9d7
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Ceedling 
|
|
2
2
|
========
|
|
3
3
|
|
|
4
|
-
**Ceedling 1.1.
|
|
4
|
+
**Ceedling 1.1.3** is the latest and greatest.
|
|
5
5
|
|
|
6
6
|
See [_Release Notes_][release-notes], [_Changelog_](docs/Changelog.md),
|
|
7
7
|
[_Breaking Changes_][breaking-changes], and [_Known Issues_][known-issues].
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* =========================================================================
|
|
2
|
+
Ceedling - Test-Centered Build System for C
|
|
3
|
+
ThrowTheSwitch.org
|
|
4
|
+
Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
|
|
5
|
+
SPDX-License-Identifier: MIT
|
|
6
|
+
========================================================================= */
|
|
7
|
+
|
|
8
|
+
#include <signal.h>
|
|
9
|
+
#include "unity.h"
|
|
10
|
+
#include "example_file.h"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
void setUp(void) {}
|
|
14
|
+
void tearDown(void) {}
|
|
15
|
+
|
|
16
|
+
void test_add_numbers_will_fail(void) {
|
|
17
|
+
// Platform-independent way of forcing a crash
|
|
18
|
+
// NOTE: Avoid `nullptr` as it is a keyword in C23
|
|
19
|
+
uint32_t* a_null_pointer = (void*)0;
|
|
20
|
+
uint32_t i = *a_null_pointer;
|
|
21
|
+
TEST_ASSERT_EQUAL_INT(2, add_numbers(i,2));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Regression coverage for issue #1185: a parameterized test defined after a
|
|
25
|
+
// crashing test in the same file must still report its own real PASS/FAIL
|
|
26
|
+
// results rather than being swept up as "crashed" alongside the actual crash.
|
|
27
|
+
TEST_CASE(5, 3, 2)
|
|
28
|
+
TEST_CASE(10, 4, 6)
|
|
29
|
+
TEST_CASE(0, 0, 0)
|
|
30
|
+
void test_difference_between_numbers_is_correct(int a, int b, int expected) {
|
|
31
|
+
TEST_ASSERT_EQUAL_INT(expected, difference_between_numbers(a, b));
|
|
32
|
+
}
|
data/bin/actions_wrapper.rb
CHANGED
|
@@ -13,16 +13,17 @@ class ActionsWrapper
|
|
|
13
13
|
include Thor::Base
|
|
14
14
|
include Thor::Actions
|
|
15
15
|
|
|
16
|
-
JUNK_FILE_EXCLUDE_REGEX =
|
|
16
|
+
JUNK_FILE_EXCLUDE_REGEX = /(\.DS_Store)|(thumbs\.db)/
|
|
17
17
|
|
|
18
18
|
# Most important mixin method is Thor::Actions class method `source_root()` we call externally
|
|
19
19
|
|
|
20
20
|
def _directory(src, *args)
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
# Build a single trailing options hash -- Thor's directory() only reads
|
|
22
|
+
# the last argument as options, so any caller-supplied hash (e.g.
|
|
23
|
+
# :force) and this default :exclude_pattern must live in the same hash.
|
|
24
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
25
|
+
options[:exclude_pattern] ||= JUNK_FILE_EXCLUDE_REGEX
|
|
26
|
+
args << options
|
|
26
27
|
|
|
27
28
|
directory( src, *args )
|
|
28
29
|
end
|
data/docs/Changelog.md
CHANGED
|
@@ -10,6 +10,14 @@ This changelog is complemented by three other documents:
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
# [1.1.3] β 2026-08-06
|
|
14
|
+
|
|
15
|
+
## πͺ Fixed
|
|
16
|
+
- [#1185](https://github.com/ThrowTheSwitch/Ceedling/issues/1185) Fixed / improved crash handling for parameterized Unity test cases.
|
|
17
|
+
- [#1186](https://github.com/ThrowTheSwitch/Ceedling/issues/1186) Fixed `gcc` version lookup for the Gcov plugin running on Windows.
|
|
18
|
+
- Ensure compilation symbols needed for Partials are injected when `:preprocess` defines are configured.
|
|
19
|
+
- Fixed use of Thorβs Actions#directory to ensure a single options hash.
|
|
20
|
+
|
|
13
21
|
# [1.1.2] β 2026-07-27
|
|
14
22
|
|
|
15
23
|
## πͺ Fixed
|
|
@@ -88,10 +88,16 @@ class Configurator
|
|
|
88
88
|
@loginator.log( "Reverted :cmock β³ :treat_inlines to :exclude because this CMock feature is superseded by Partials.", Verbosity::COMPLAIN, LogLabels::NOTICE )
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# If partials enabled, inject partials name prefix symbol to all test compilation
|
|
91
|
+
# If partials enabled, inject partials name prefix symbol to all test compilation
|
|
92
|
+
# and, if a project defines its own preprocess-scoped defines matcher, to
|
|
93
|
+
# preprocessing as well -- `:preprocess:` defines are looked up independently
|
|
94
|
+
# of `:test:` defines and only fall back to the latter when `:preprocess:`
|
|
95
|
+
# is absent entirely, so a project-defined `:preprocess:` matcher needs this
|
|
96
|
+
# symbol appended directly or Partials' own macro-based #includes break.
|
|
92
97
|
# Handle both the simple list and matcher hash config formats.
|
|
93
98
|
_partials_prefix_symbol = "CEEDLING_PARTIALS_PREFIX=#{PARTIAL_FILENAME_PREFIX}"
|
|
94
99
|
ConfigMatchinator.append_matcher_entries( config[:defines][:test], _partials_prefix_symbol )
|
|
100
|
+
ConfigMatchinator.append_matcher_entries( config[:defines][:preprocess], _partials_prefix_symbol )
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
|
data/lib/ceedling/defaults.rb
CHANGED
|
@@ -69,7 +69,10 @@ DEFAULT_TEST_FIXTURE_SIMPLE_BACKTRACE_TOOL = {
|
|
|
69
69
|
:name => 'default_test_fixture_simple_backtrace'.freeze,
|
|
70
70
|
:optional => false.freeze,
|
|
71
71
|
:arguments => [
|
|
72
|
-
|
|
72
|
+
# Test case matching flag and (quoted) value, fully composed by the caller as
|
|
73
|
+
# either `-n "<exact name>"` or `-f "<base name>"` -- see
|
|
74
|
+
# GeneratorTestResultsBacktrace#unity_filter_arg.
|
|
75
|
+
'${2}'.freeze
|
|
73
76
|
].freeze
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -202,7 +205,10 @@ DEFAULT_TEST_BACKTRACE_GDB_TOOL = {
|
|
|
202
205
|
"--command \"${1}\"".freeze, # Debug script file to run
|
|
203
206
|
'--args'.freeze,
|
|
204
207
|
'${2}'.freeze, # Test executable
|
|
205
|
-
|
|
208
|
+
# Test case matching flag and (quoted) value, fully composed by the caller as
|
|
209
|
+
# either `-n "<exact name>"` or `-f "<base name>"` -- see
|
|
210
|
+
# GeneratorTestResultsBacktrace#unity_filter_arg.
|
|
211
|
+
'${3}'.freeze
|
|
206
212
|
].freeze
|
|
207
213
|
}
|
|
208
214
|
|
|
@@ -13,7 +13,8 @@ class GeneratorTestResultsBacktrace
|
|
|
13
13
|
@RESULTS_COLLECTOR = Struct.new( :passed, :failed, :ignored, :output, keyword_init:true )
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# Re-runs each test case
|
|
16
|
+
# Re-runs each test case (or, for a parameterized test, each group of parameterized
|
|
17
|
+
# cases -- see `group_test_cases`) under gdb to identify which one(s) crashed and why.
|
|
17
18
|
# Writes the full gdb transcript to a per-test-case log file and assembles a
|
|
18
19
|
# terse crash label (signal + description, optional source line in backticks)
|
|
19
20
|
# for each failing test case. Returns a modified shell_result with regenerated output.
|
|
@@ -28,54 +29,83 @@ class GeneratorTestResultsBacktrace
|
|
|
28
29
|
|
|
29
30
|
test_name = File.basename( filename, '.*' )
|
|
30
31
|
|
|
31
|
-
# Iterate on test cases
|
|
32
|
-
test_cases.each do |
|
|
33
|
-
#
|
|
34
|
-
log_path = @file_path_utils.form_test_gdb_log( test_name, context: context, name: test_case[:test] )
|
|
35
|
-
@file_wrapper.mkdir( File.dirname( log_path ) )
|
|
36
|
-
|
|
37
|
-
# Build the test fixture to run with our test case of interest
|
|
32
|
+
# Iterate on test cases, one sub-process run per group (see `group_test_cases`)
|
|
33
|
+
group_test_cases( test_cases ).each do |group|
|
|
34
|
+
# Build the test fixture to run with our test case (or parameterized group) of interest
|
|
38
35
|
command = @tool_executor.build_command_line(
|
|
39
36
|
@configurator.tools_test_backtrace_gdb, [],
|
|
40
37
|
gdb_script_filepath,
|
|
41
38
|
executable,
|
|
42
|
-
|
|
39
|
+
unity_filter_arg( group )
|
|
43
40
|
)
|
|
44
41
|
# Things are gonna go boom, so ignore booms to get output
|
|
45
42
|
command[:options][:boom] = false
|
|
46
43
|
|
|
47
44
|
crash_result = @tool_executor.exec( command )
|
|
48
45
|
|
|
49
|
-
# Sum execution time for each
|
|
46
|
+
# Sum execution time for each sub-process run
|
|
50
47
|
# Note: Running tests separately increases total execution time
|
|
51
48
|
shell_result[:time] += crash_result[:time].to_f()
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
unresolved = []
|
|
54
51
|
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
# Attribute each group member its own real result line, if Unity printed one
|
|
53
|
+
group.each do |test_case|
|
|
54
|
+
case crash_result[:output]
|
|
55
|
+
# Success test case
|
|
56
|
+
when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:PASS\s*$)/
|
|
57
|
+
test_case_results[:passed] += 1
|
|
58
|
+
test_case_results[:output] << $1
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
# Ignored test case
|
|
61
|
+
when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:IGNORE\s*$)/
|
|
62
|
+
test_case_results[:ignored] += 1
|
|
63
|
+
test_case_results[:output] << $1
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:FAIL(:.+)?\s*$)/
|
|
66
|
+
test_case_results[:failed] += 1
|
|
67
|
+
test_case_results[:output] << $1
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
# No result line for this member -- either it crashed, or it never got to run
|
|
70
|
+
# because an earlier member in this same group crashed. Resolved below.
|
|
71
|
+
else
|
|
72
|
+
unresolved << test_case
|
|
73
|
+
end
|
|
74
|
+
end
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
@file_wrapper.write( log_path, "=== #{test_case[:test]} ===\n#{crash_result[:output]}\n", 'a' )
|
|
76
|
+
next if unresolved.empty?
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
# Prefer whichever unresolved member's own C symbol is actually named in the gdb
|
|
79
|
+
# backtrace (works regardless of position in the group); fall back to the first
|
|
80
|
+
# unresolved member if no member's symbol can be found in the transcript (e.g. a
|
|
81
|
+
# brief crash report with no frame information at all).
|
|
82
|
+
crashed_case = unresolved.find do |tc|
|
|
83
|
+
crash_result[:output].match?( /#{Regexp.escape(tc[:symbol])}\s*\(\)\sat/ )
|
|
84
|
+
end
|
|
85
|
+
crashed_case ||= unresolved.first
|
|
86
|
+
|
|
87
|
+
# Per-test-case log file: <log_path>/<context>/<test_name>/<test_case>.gdb.log
|
|
88
|
+
log_path = @file_path_utils.form_test_gdb_log( test_name, context: context, name: crashed_case[:test] )
|
|
89
|
+
@file_wrapper.mkdir( File.dirname( log_path ) )
|
|
90
|
+
@file_wrapper.write( log_path, "=== #{crashed_case[:test]} ===\n#{crash_result[:output]}\n", 'a' )
|
|
91
|
+
|
|
92
|
+
unresolved.each do |test_case|
|
|
93
|
+
test_case_results[:failed] += 1
|
|
94
|
+
|
|
95
|
+
if !test_case.equal?( crashed_case )
|
|
96
|
+
# An earlier case in this same parameterized group already crashed the
|
|
97
|
+
# process -- this member never got a chance to run.
|
|
98
|
+
test_case_results[:output] <<
|
|
99
|
+
"#{filename}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: " \
|
|
100
|
+
"Test case not run -- an earlier case in this parameterized test group crashed"
|
|
101
|
+
next
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Collect file_name and line in which crash occurred.
|
|
105
|
+
# Match against the actual C symbol (`:symbol`), not the human-facing test name
|
|
106
|
+
# (`:test`): a parameterized test case crashes inside a generated wrapper function
|
|
107
|
+
# (`runner_args<N>_<test>`), not a function literally named `<test>(<args>)`.
|
|
108
|
+
matched = crash_result[:output].match( /#{Regexp.escape(test_case[:symbol])}\s*\(\)\sat.+#{filename}:(\d+)\n/ )
|
|
79
109
|
|
|
80
110
|
# If we found an error report line containing `test_case() at filename.c:###` in `gdb` output
|
|
81
111
|
if matched
|
|
@@ -86,7 +116,7 @@ class GeneratorTestResultsBacktrace
|
|
|
86
116
|
signal_label = format_signal_label( crash_result[:output] )
|
|
87
117
|
|
|
88
118
|
# Extract the offending source line (nil for assertion crashes or when unavailable)
|
|
89
|
-
source_line = extract_source_line( crash_result[:output], test_case[:
|
|
119
|
+
source_line = extract_source_line( crash_result[:output], test_case[:symbol], filename )
|
|
90
120
|
|
|
91
121
|
# Unity's test executable output is line oriented.
|
|
92
122
|
# Multi-line output is not possible (it looks like random `printf()` statements to the results parser).
|
|
@@ -94,7 +124,7 @@ class GeneratorTestResultsBacktrace
|
|
|
94
124
|
crash_detail = source_line ? "#{NEWLINE_TOKEN}`#{source_line}`" : ''
|
|
95
125
|
|
|
96
126
|
# Log path appears on its own encoded line so the results parser treats it separately
|
|
97
|
-
|
|
127
|
+
test_case_results[:output] <<
|
|
98
128
|
"#{filename}:#{line_number}:#{test_case[:test]}:FAIL: Test case crashed" \
|
|
99
129
|
" >> #{signal_label}" \
|
|
100
130
|
"#{crash_detail}" \
|
|
@@ -106,20 +136,18 @@ class GeneratorTestResultsBacktrace
|
|
|
106
136
|
label = format_signal_label( crash_result[:output] )
|
|
107
137
|
|
|
108
138
|
if !label.empty?
|
|
109
|
-
|
|
139
|
+
test_case_results[:output] <<
|
|
110
140
|
"#{filename}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: Test case crashed" \
|
|
111
141
|
" >> #{label}" \
|
|
112
142
|
"#{NEWLINE_TOKEN}(#{log_path})"
|
|
113
143
|
else
|
|
114
|
-
|
|
144
|
+
test_case_results[:output] <<
|
|
115
145
|
"#{filename}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: " \
|
|
116
146
|
"Test case crashed (failed to extract `gdb` report)" \
|
|
117
147
|
"#{NEWLINE_TOKEN}(#{log_path})"
|
|
118
148
|
end
|
|
119
149
|
end
|
|
120
150
|
end
|
|
121
|
-
|
|
122
|
-
test_case_results[:output] << test_output
|
|
123
151
|
end
|
|
124
152
|
|
|
125
153
|
# Reset shell result exit code and output
|
|
@@ -135,7 +163,8 @@ class GeneratorTestResultsBacktrace
|
|
|
135
163
|
return shell_result
|
|
136
164
|
end
|
|
137
165
|
|
|
138
|
-
# Re-runs each test case
|
|
166
|
+
# Re-runs each test case (or, for a parameterized test, each group of parameterized
|
|
167
|
+
# cases -- see `group_test_cases`) individually to determine which one(s) crashed.
|
|
139
168
|
# For crash cases, captures any extra output from the test binary (e.g.
|
|
140
169
|
# assertion messages on stderr) and includes it in the failure report.
|
|
141
170
|
# Returns a modified shell_result with regenerated output.
|
|
@@ -146,50 +175,61 @@ class GeneratorTestResultsBacktrace
|
|
|
146
175
|
# Reset time
|
|
147
176
|
shell_result[:time] = 0
|
|
148
177
|
|
|
149
|
-
# Iterate on test cases
|
|
150
|
-
test_cases.each do |
|
|
151
|
-
# Build the test fixture to run with our test case of interest
|
|
178
|
+
# Iterate on test cases, one sub-process run per group (see `group_test_cases`)
|
|
179
|
+
group_test_cases( test_cases ).each do |group|
|
|
180
|
+
# Build the test fixture to run with our test case (or parameterized group) of interest
|
|
152
181
|
command = @tool_executor.build_command_line(
|
|
153
182
|
@configurator.tools_test_fixture_simple_backtrace, [],
|
|
154
183
|
executable,
|
|
155
|
-
|
|
184
|
+
unity_filter_arg( group )
|
|
156
185
|
)
|
|
157
186
|
# Things are gonna go boom, so ignore booms to get output
|
|
158
187
|
command[:options][:boom] = false
|
|
159
188
|
|
|
160
189
|
crash_result = @tool_executor.exec( command )
|
|
161
190
|
|
|
162
|
-
# Sum execution time for each
|
|
191
|
+
# Sum execution time for each sub-process run
|
|
163
192
|
# Note: Running tests separately increases total execution time
|
|
164
193
|
shell_result[:time] += crash_result[:time].to_f()
|
|
165
194
|
|
|
166
|
-
#
|
|
167
|
-
|
|
168
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
test_case_results[:ignored] += 1
|
|
176
|
-
test_output = $1 # Grab regex match
|
|
177
|
-
|
|
178
|
-
when /(^#{filename}.+:FAIL(:.+)?\s*$)/
|
|
179
|
-
test_case_results[:failed] += 1
|
|
180
|
-
test_output = $1 # Grab regex match
|
|
181
|
-
|
|
182
|
-
else # Crash failure case
|
|
183
|
-
test_case_results[:failed] += 1
|
|
184
|
-
|
|
185
|
-
# Collect any non-result, non-blank lines (e.g. assertion messages on stderr)
|
|
186
|
-
extra = extract_simple_crash_output( crash_result[:output], filename )
|
|
187
|
-
test_output = "#{filename}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: Test case crashed"
|
|
188
|
-
test_output += " >> #{extra.join(NEWLINE_TOKEN)}" unless extra.empty?
|
|
189
|
-
end
|
|
195
|
+
crashed = false # Has the actual crash in this group already been attributed?
|
|
196
|
+
|
|
197
|
+
# Attribute each group member its own real result line, if Unity printed one
|
|
198
|
+
group.each do |test_case|
|
|
199
|
+
case crash_result[:output]
|
|
200
|
+
# Success test case
|
|
201
|
+
when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:PASS\s*$)/
|
|
202
|
+
test_case_results[:passed] += 1
|
|
203
|
+
test_case_results[:output] << $1
|
|
190
204
|
|
|
191
|
-
|
|
192
|
-
|
|
205
|
+
# Ignored test case
|
|
206
|
+
when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:IGNORE\s*$)/
|
|
207
|
+
test_case_results[:ignored] += 1
|
|
208
|
+
test_case_results[:output] << $1
|
|
209
|
+
|
|
210
|
+
when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:FAIL(:.+)?\s*$)/
|
|
211
|
+
test_case_results[:failed] += 1
|
|
212
|
+
test_case_results[:output] << $1
|
|
213
|
+
|
|
214
|
+
# No result line for this member -- either it crashed, or it never got to run
|
|
215
|
+
# because an earlier member in this same group crashed.
|
|
216
|
+
else
|
|
217
|
+
test_case_results[:failed] += 1
|
|
218
|
+
|
|
219
|
+
if crashed
|
|
220
|
+
test_case_results[:output] <<
|
|
221
|
+
"#{filename}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: " \
|
|
222
|
+
"Test case not run -- an earlier case in this parameterized test group crashed"
|
|
223
|
+
else
|
|
224
|
+
crashed = true
|
|
225
|
+
# Collect any non-result, non-blank lines (e.g. assertion messages on stderr)
|
|
226
|
+
extra = extract_simple_crash_output( crash_result[:output], filename )
|
|
227
|
+
test_output = "#{filename}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: Test case crashed"
|
|
228
|
+
test_output += " >> #{extra.join(NEWLINE_TOKEN)}" unless extra.empty?
|
|
229
|
+
test_case_results[:output] << test_output
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
193
233
|
end
|
|
194
234
|
|
|
195
235
|
# Reset shell result exit code and output
|
|
@@ -208,6 +248,34 @@ class GeneratorTestResultsBacktrace
|
|
|
208
248
|
### Private ###
|
|
209
249
|
private
|
|
210
250
|
|
|
251
|
+
# Groups test cases so each parameterized test's cases are isolated together as one
|
|
252
|
+
# sub-process run instead of one run per case. Unity's `-n`/`-f` command-line filter
|
|
253
|
+
# parser treats a comma as a separator between multiple OR'd filter clauses, so an
|
|
254
|
+
# exact filter can never match a parameterized case's runtime name once it has more
|
|
255
|
+
# than one argument (e.g. `name(5, 3, 2)`). Grouping by base name and matching each
|
|
256
|
+
# member's own result line out of the group's single shared sub-run output sidesteps
|
|
257
|
+
# that limitation entirely. Non-parameterized test cases are unaffected: each forms
|
|
258
|
+
# its own single-member group, isolated exactly as before.
|
|
259
|
+
def group_test_cases(test_cases)
|
|
260
|
+
test_cases.group_by { |test_case| test_case[:test].sub(/\(.*\)\z/, '') }.values
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Builds the Unity command-line filter argument for isolating one group (see
|
|
264
|
+
# `group_test_cases`). A non-parameterized (single-member, no-args) group is isolated
|
|
265
|
+
# with an exact-match filter as before. A parameterized group is isolated with a
|
|
266
|
+
# non-strict prefix filter on its shared base name -- the only reliable way to select
|
|
267
|
+
# such a group, since its members' runtime names contain commas.
|
|
268
|
+
def unity_filter_arg(group)
|
|
269
|
+
test_name = group.first[:test]
|
|
270
|
+
|
|
271
|
+
if test_name.include?('(')
|
|
272
|
+
base_name = test_name.sub(/\(.*\)\z/, '')
|
|
273
|
+
%(-f "#{base_name}")
|
|
274
|
+
else
|
|
275
|
+
%(-n "#{test_name}")
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
211
279
|
# Builds a terse crash label from gdb output.
|
|
212
280
|
# Rules:
|
|
213
281
|
# - Named signal explicitly in output β "[SIGNAL] Description"
|
|
@@ -62,8 +62,26 @@ class GeneratorTestRunner
|
|
|
62
62
|
# Unity's runner generator `find_tests()` produces an array of hashes with the following keys...
|
|
63
63
|
# { test:, args:, call:, params:, line_number: }
|
|
64
64
|
|
|
65
|
-
# For external use
|
|
66
|
-
|
|
65
|
+
# For external use, reduce down to test name, runtime C symbol, and line number.
|
|
66
|
+
# A parameterized test (`:args` populated) is expanded into one entry per TEST_CASE /
|
|
67
|
+
# TEST_RANGE / TEST_MATRIX row, since Unity's generated runner registers each invocation
|
|
68
|
+
# under its own name (matching `generate_test_runner.rb`'s `"#{test}(#{args})"` naming) and
|
|
69
|
+
# runs it through its own wrapper function (`runner_args<N>_<test>`). Collapsing these to a
|
|
70
|
+
# single bare-name entry breaks exact-match test case isolation (crash handling) for
|
|
71
|
+
# parameterized tests.
|
|
72
|
+
@test_cases = @test_cases_internal.flat_map do |hash|
|
|
73
|
+
if hash[:args].nil? || hash[:args].empty?
|
|
74
|
+
[ { test: hash[:test], symbol: hash[:test], line_number: hash[:line_number] } ]
|
|
75
|
+
else
|
|
76
|
+
hash[:args].each_with_index.map do |args, idx|
|
|
77
|
+
{
|
|
78
|
+
test: "#{hash[:test]}(#{args})",
|
|
79
|
+
symbol: "runner_args#{idx + 1}_#{hash[:test]}",
|
|
80
|
+
line_number: hash[:line_number]
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
67
85
|
end
|
|
68
86
|
|
|
69
87
|
def extract_test_cases(source_contents)
|
data/lib/version.rb
CHANGED
data/plugins/gcov/lib/gcov.rb
CHANGED
|
@@ -354,8 +354,8 @@ class Gcov < Plugin
|
|
|
354
354
|
|
|
355
355
|
shell_result = @tool_executor.exec( command )
|
|
356
356
|
|
|
357
|
-
# First line of gcc --version: "gcc (...platform info...) major.minor.patch"
|
|
358
|
-
version_match = shell_result[:output].match(/^gcc
|
|
357
|
+
# First line of gcc --version: "gcc[.exe] (...platform info...) major.minor.patch"
|
|
358
|
+
version_match = shell_result[:output].match(/^gcc(?:#{Regexp.escape(EXTENSION_WIN_EXE)})?\s+.*\s+(\d+)\.(\d+)\.\d+/)
|
|
359
359
|
|
|
360
360
|
if version_match.nil? || version_match[1].nil? || version_match[2].nil?
|
|
361
361
|
raise CeedlingException.new("Could not collect `gcc` version from its command line")
|
data/site-local/sitemap.xml.gz
CHANGED
|
Binary file
|
|
@@ -279,6 +279,21 @@ class SystemContext
|
|
|
279
279
|
sections << stderr.strip
|
|
280
280
|
end
|
|
281
281
|
|
|
282
|
+
# Loginator routes `Verbosity::DEBUG` messages (including the backtrace
|
|
283
|
+
# `log_debug_backtrace` emits for a caught exception) to stdout rather than
|
|
284
|
+
# stderr, and this method's stdout scan above only pulls lines containing
|
|
285
|
+
# the literal substrings 'ERROR'/'EXCEPTION' -- a bare backtrace line
|
|
286
|
+
# (file:line:in `method') contains neither, so without surfacing it here
|
|
287
|
+
# explicitly, the one piece of stdout most useful for diagnosing *why* a
|
|
288
|
+
# command failed would otherwise only ever reach the full raw-output log
|
|
289
|
+
# file, not this console-visible summary (what CI output actually shows).
|
|
290
|
+
label = 'Debug Backtrace ==>'
|
|
291
|
+
backtrace_block = extract_section(stdout, label)
|
|
292
|
+
unless backtrace_block.empty?
|
|
293
|
+
sections << ">> DEBUG BACKTRACE"
|
|
294
|
+
sections.concat(backtrace_block)
|
|
295
|
+
end
|
|
296
|
+
|
|
282
297
|
label = 'FAILED TEST SUMMARY'
|
|
283
298
|
failed_block = extract_section(stdout, label)
|
|
284
299
|
unless failed_block.empty?
|
|
@@ -91,6 +91,7 @@ ceedling_system_tests do
|
|
|
91
91
|
test_case :crash_none_writes_fail_results_file
|
|
92
92
|
test_case :crash_simple_sigsegv_all_test_cases
|
|
93
93
|
test_case :crash_simple_sigabrt_assert_failure
|
|
94
|
+
test_case :crash_simple_sigsegv_with_parameterized_test
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
describe "Backtrace with GDB" do
|
|
@@ -100,6 +101,7 @@ ceedling_system_tests do
|
|
|
100
101
|
test_case :crash_gdb_sigsegv_targets_test_case_filter
|
|
101
102
|
test_case :crash_gdb_sigsegv_excludes_test_case_filter
|
|
102
103
|
test_case :crash_gdb_sigabrt_assert_failure
|
|
104
|
+
test_case :crash_gdb_sigsegv_with_parameterized_test
|
|
103
105
|
end
|
|
104
106
|
|
|
105
107
|
describe "Test filtering" do
|
|
@@ -94,6 +94,7 @@ ceedling_system_tests do
|
|
|
94
94
|
test_case :crash_none_writes_fail_results_file
|
|
95
95
|
test_case :crash_simple_sigsegv_all_test_cases
|
|
96
96
|
test_case :crash_simple_sigabrt_assert_failure
|
|
97
|
+
test_case :crash_simple_sigsegv_with_parameterized_test
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
describe "Backtrace with GDB" do
|
|
@@ -103,6 +104,7 @@ ceedling_system_tests do
|
|
|
103
104
|
test_case :crash_gdb_sigsegv_targets_test_case_filter
|
|
104
105
|
test_case :crash_gdb_sigsegv_excludes_test_case_filter
|
|
105
106
|
test_case :crash_gdb_sigabrt_assert_failure
|
|
107
|
+
test_case :crash_gdb_sigsegv_with_parameterized_test
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
describe "Test filtering" do
|
|
@@ -892,6 +892,96 @@ module CommonSystemTestCases
|
|
|
892
892
|
end
|
|
893
893
|
end
|
|
894
894
|
|
|
895
|
+
# Regression test for issue #1185: a parameterized test (TEST_CASE) defined after a
|
|
896
|
+
# crashing test in the same file must still report its own real PASS results instead
|
|
897
|
+
# of being swept up as "crashed" alongside the actual crash. Uses :use_backtrace => :simple
|
|
898
|
+
# (Ceedling's default), which is the mode in which the bug was reported.
|
|
899
|
+
def crash_simple_sigsegv_with_parameterized_test
|
|
900
|
+
@c.with_context do
|
|
901
|
+
Dir.chdir @proj_name do
|
|
902
|
+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
|
|
903
|
+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
904
|
+
FileUtils.cp test_asset_path("test_example_file_crash_sigsegv_with_param.c"), 'test/'
|
|
905
|
+
|
|
906
|
+
@c.merge_project_yml_for_test({
|
|
907
|
+
:project => { :use_backtrace => :simple },
|
|
908
|
+
:unity => { :use_param_tests => true }
|
|
909
|
+
})
|
|
910
|
+
|
|
911
|
+
output = @c.ceedling_build_exec("test:all")
|
|
912
|
+
expect(@c.last_exit_status).to eq(1) # Test should fail because of crash
|
|
913
|
+
expect(output).to match(/Unit test failures/)
|
|
914
|
+
# 1 crashing test + 3 parameterized cases
|
|
915
|
+
expect(output).to match(/TESTED:\s+4/)
|
|
916
|
+
expect(output).to match(/PASSED:\s+3/)
|
|
917
|
+
expect(output).to match(/FAILED:\s+1/)
|
|
918
|
+
expect(output).to match(/IGNORED:\s+0/)
|
|
919
|
+
|
|
920
|
+
result_file = './build/test/results/test_example_file_crash_sigsegv_with_param.fail'
|
|
921
|
+
expect(File.exist?(result_file)).to be(true)
|
|
922
|
+
results = YamlWrapper.new.load(result_file)
|
|
923
|
+
|
|
924
|
+
# Only the truly crashing test is reported as a failure/crash
|
|
925
|
+
expect(results[:failures].map { |f| f[:test] }).to eq(['test_add_numbers_will_fail'])
|
|
926
|
+
expect(results[:failures].first[:message]).to match(/Test case crashed/)
|
|
927
|
+
|
|
928
|
+
# Each parameterized case reports its own real (passing) result, by name, not a crash
|
|
929
|
+
expect(results[:successes].map { |s| s[:test] }).to contain_exactly(
|
|
930
|
+
'test_difference_between_numbers_is_correct(5, 3, 2)',
|
|
931
|
+
'test_difference_between_numbers_is_correct(10, 4, 6)',
|
|
932
|
+
'test_difference_between_numbers_is_correct(0, 0, 0)'
|
|
933
|
+
)
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
# Same regression coverage as `crash_simple_sigsegv_with_parameterized_test`, but for
|
|
939
|
+
# :use_backtrace => :gdb: confirms gdb-based isolation also correctly distinguishes the
|
|
940
|
+
# crashing test from the unrelated parameterized test, and still attributes the crash to
|
|
941
|
+
# the correct source line (exercising the gdb crash-frame symbol matching fix).
|
|
942
|
+
def crash_gdb_sigsegv_with_parameterized_test
|
|
943
|
+
@c.with_context do
|
|
944
|
+
Dir.chdir @proj_name do
|
|
945
|
+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
|
|
946
|
+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
947
|
+
FileUtils.cp test_asset_path("test_example_file_crash_sigsegv_with_param.c"), 'test/'
|
|
948
|
+
|
|
949
|
+
@c.merge_project_yml_for_test({
|
|
950
|
+
:project => { :use_backtrace => :gdb },
|
|
951
|
+
:unity => { :use_param_tests => true }
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
output = @c.ceedling_build_exec("test:all")
|
|
955
|
+
expect(@c.last_exit_status).to eq(1) # Test should fail because of crash
|
|
956
|
+
expect(output).to match(/Unit test failures/)
|
|
957
|
+
expect(output).to match(/TESTED:\s+4/)
|
|
958
|
+
expect(output).to match(/PASSED:\s+3/)
|
|
959
|
+
expect(output).to match(/FAILED:\s+1/)
|
|
960
|
+
expect(output).to match(/IGNORED:\s+0/)
|
|
961
|
+
|
|
962
|
+
result_file = './build/test/results/test_example_file_crash_sigsegv_with_param.fail'
|
|
963
|
+
expect(File.exist?(result_file)).to be(true)
|
|
964
|
+
results = YamlWrapper.new.load(result_file)
|
|
965
|
+
|
|
966
|
+
# Only the truly crashing test is reported as a failure/crash
|
|
967
|
+
expect(results[:failures].map { |f| f[:test] }).to eq(['test_add_numbers_will_fail'])
|
|
968
|
+
expect(results[:failures].first[:message]).to match(/Test case crashed/)
|
|
969
|
+
expect(results[:failures].first[:message]).to match(/SIGSEGV/i)
|
|
970
|
+
|
|
971
|
+
# Each parameterized case reports its own real (passing) result, by name, not a crash
|
|
972
|
+
expect(results[:successes].map { |s| s[:test] }).to contain_exactly(
|
|
973
|
+
'test_difference_between_numbers_is_correct(5, 3, 2)',
|
|
974
|
+
'test_difference_between_numbers_is_correct(10, 4, 6)',
|
|
975
|
+
'test_difference_between_numbers_is_correct(0, 0, 0)'
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
log_path = './build/logs/test/test_example_file_crash_sigsegv_with_param/test_add_numbers_will_fail.gdb.log'
|
|
979
|
+
expect(File.exist?(log_path)).to be(true)
|
|
980
|
+
expect(File.read(log_path)).to match(/SIGSEGV|Segmentation fault/i)
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
end
|
|
984
|
+
|
|
895
985
|
def project_with_test_file_directly_including_source_file
|
|
896
986
|
@c.with_context do
|
|
897
987
|
Dir.chdir @proj_name do
|
|
@@ -9,6 +9,7 @@ require 'spec_helper'
|
|
|
9
9
|
require 'ceedling/config/configurator'
|
|
10
10
|
require 'ceedling/ruby_expandinator'
|
|
11
11
|
require 'ceedling/exceptions'
|
|
12
|
+
require 'ceedling/constants'
|
|
12
13
|
|
|
13
14
|
describe Configurator do
|
|
14
15
|
|
|
@@ -184,4 +185,48 @@ describe Configurator do
|
|
|
184
185
|
|
|
185
186
|
end
|
|
186
187
|
|
|
188
|
+
describe "#set_partials_derived_config" do
|
|
189
|
+
|
|
190
|
+
def partials_config
|
|
191
|
+
{ project: { use_partials: true }, defines: { test: ['TEST'] }, cmock: {} }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "does nothing when :use_partials is disabled" do
|
|
195
|
+
config = partials_config
|
|
196
|
+
config[:project][:use_partials] = false
|
|
197
|
+
|
|
198
|
+
@configurator.set_partials_derived_config( config )
|
|
199
|
+
|
|
200
|
+
expect( config[:defines][:test] ).to eq( ['TEST'] )
|
|
201
|
+
expect( config[:defines] ).to_not have_key( :preprocess )
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "appends the partials prefix symbol to a simple :test: defines list" do
|
|
205
|
+
config = partials_config
|
|
206
|
+
|
|
207
|
+
@configurator.set_partials_derived_config( config )
|
|
208
|
+
|
|
209
|
+
expect( config[:defines][:test] ).to include( "CEEDLING_PARTIALS_PREFIX=#{PARTIAL_FILENAME_PREFIX}" )
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "leaves :preprocess: defines untouched when the project does not define that section" do
|
|
213
|
+
config = partials_config
|
|
214
|
+
|
|
215
|
+
@configurator.set_partials_derived_config( config )
|
|
216
|
+
|
|
217
|
+
expect( config[:defines] ).to_not have_key( :preprocess )
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "appends the partials prefix symbol under the :* matcher when the project defines its own :preprocess: matcher hash" do
|
|
221
|
+
config = partials_config
|
|
222
|
+
config[:defines][:preprocess] = { 'TestSoilMoisture.c' => ['CEEDLING_DELTA_PROBE'] }
|
|
223
|
+
|
|
224
|
+
@configurator.set_partials_derived_config( config )
|
|
225
|
+
|
|
226
|
+
expect( config[:defines][:preprocess][:*] ).to include( "CEEDLING_PARTIALS_PREFIX=#{PARTIAL_FILENAME_PREFIX}" )
|
|
227
|
+
expect( config[:defines][:preprocess]['TestSoilMoisture.c'] ).to eq( ['CEEDLING_DELTA_PROBE'] )
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
end
|
|
231
|
+
|
|
187
232
|
end
|
|
@@ -65,6 +65,19 @@ GDB_NO_SIGNAL_OUTPUT = <<~GDB.freeze
|
|
|
65
65
|
Inferior 1 (process 12345) exited with code 0139.
|
|
66
66
|
GDB
|
|
67
67
|
|
|
68
|
+
# Parameterized test crash (issue #1185): the crashing frame names the generated
|
|
69
|
+
# wrapper function (`runner_args1_test_value_out_of_range_good`), not the human-facing
|
|
70
|
+
# test name (`test_value_out_of_range_good(101, 1)`) that Unity reports at runtime.
|
|
71
|
+
GDB_SIGSEGV_PARAM_OUTPUT = <<~GDB.freeze
|
|
72
|
+
[Thread debugging using libthread_db enabled]
|
|
73
|
+
|
|
74
|
+
Program received signal SIGSEGV, Segmentation fault.
|
|
75
|
+
0x00005618066ea1fb in runner_args1_test_value_out_of_range_good () at test/test_module_d.c:157
|
|
76
|
+
157 int invalid = 1 / 0;
|
|
77
|
+
#0 0x00005618066ea1fb in runner_args1_test_value_out_of_range_good () at test/test_module_d.c:157
|
|
78
|
+
#1 0x00005618066eb4de in run_test (func=0x5618066ea1e7 <runner_args1_test_value_out_of_range_good>, name=0x5618066eb2e0 "test_value_out_of_range_good(101, 1)", line_num=157) at build/test/runners/test_module_d_runner.c:76
|
|
79
|
+
GDB
|
|
80
|
+
|
|
68
81
|
# Windows SIGSEGV β Thread N prefix, space-padded source line, DLL frames
|
|
69
82
|
# Single-quoted heredoc: backslashes in Windows paths are literal, not escape sequences
|
|
70
83
|
GDB_WINDOWS_SIGSEGV_OUTPUT = <<~'GDB'.freeze
|
|
@@ -153,7 +166,7 @@ describe GeneratorTestResultsBacktrace do
|
|
|
153
166
|
let(:filename) { 'test_lib.c' }
|
|
154
167
|
let(:executable) { 'build/test/out/test_lib/test_lib.out' }
|
|
155
168
|
let(:shell_result){ { exit_code: 1, output: '', time: 0.0 } }
|
|
156
|
-
let(:test_cases) { [{ test: 'test_asserting', line_number: 8 }] }
|
|
169
|
+
let(:test_cases) { [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }] }
|
|
157
170
|
|
|
158
171
|
before(:each) do
|
|
159
172
|
allow(@configurator).to receive(:project_build_tests_root).and_return('build/test')
|
|
@@ -194,7 +207,7 @@ describe GeneratorTestResultsBacktrace do
|
|
|
194
207
|
end
|
|
195
208
|
|
|
196
209
|
it 'handles a SIGSEGV crash β writes log, includes signal label, backtick source line, and log path' do
|
|
197
|
-
test_cases_sigsegv = [{ test: 'testCrash', line_number: 37 }]
|
|
210
|
+
test_cases_sigsegv = [{ test: 'testCrash', symbol: 'testCrash', line_number: 37 }]
|
|
198
211
|
filename_sigsegv = 'TestUsartModel.c'
|
|
199
212
|
|
|
200
213
|
allow(@tool_executor).to receive(:exec)
|
|
@@ -219,8 +232,39 @@ describe GeneratorTestResultsBacktrace do
|
|
|
219
232
|
expect(crash_line).to include('(/build/logs/test/TestUsartModel/testCrash.gdb.log)')
|
|
220
233
|
end
|
|
221
234
|
|
|
222
|
-
it '
|
|
223
|
-
|
|
235
|
+
it 'attributes a crash in a parameterized test to its wrapper symbol, not its display name' do
|
|
236
|
+
test_cases_param = [{
|
|
237
|
+
test: 'test_value_out_of_range_good(101, 1)',
|
|
238
|
+
symbol: 'runner_args1_test_value_out_of_range_good',
|
|
239
|
+
line_number: 157
|
|
240
|
+
}]
|
|
241
|
+
filename_param = 'test_module_d.c'
|
|
242
|
+
|
|
243
|
+
allow(@tool_executor).to receive(:exec)
|
|
244
|
+
.and_return({ output: GDB_SIGSEGV_PARAM_OUTPUT, time: 0.5, exit_code: 139 })
|
|
245
|
+
|
|
246
|
+
expected_output_lines = []
|
|
247
|
+
allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
|
|
248
|
+
expected_output_lines = kwargs[:output]
|
|
249
|
+
'regenerated'
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
expect(@file_wrapper).to receive(:write)
|
|
253
|
+
.with('/build/logs/test/test_module_d/test_value_out_of_range_good(101, 1).gdb.log', /=== test_value_out_of_range_good\(101, 1\) ===/, 'a')
|
|
254
|
+
|
|
255
|
+
@backtrace.do_gdb( filename_param, executable, shell_result, test_cases_param, context: :test )
|
|
256
|
+
|
|
257
|
+
crash_line = expected_output_lines.first
|
|
258
|
+
# The FAIL line reports the human-facing display name (with args)...
|
|
259
|
+
expect(crash_line).to start_with('test_module_d.c:157:test_value_out_of_range_good(101, 1):FAIL: Test case crashed')
|
|
260
|
+
# ...but the crash frame was still correctly located via the wrapper symbol.
|
|
261
|
+
expect(crash_line).to include('>> [SIGSEGV] Segmentation fault')
|
|
262
|
+
expect(crash_line).to include("#{NEWLINE_TOKEN}`int invalid = 1 / 0;`")
|
|
263
|
+
expect(crash_line).not_to include('failed to extract')
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'handles a SIGABRT crash from assert() β shows assertion text, no source line' do
|
|
267
|
+
test_cases_assert = [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }]
|
|
224
268
|
|
|
225
269
|
allow(@tool_executor).to receive(:exec)
|
|
226
270
|
.and_return({ output: GDB_SIGABRT_ASSERT_OUTPUT, time: 0.3, exit_code: 134 })
|
|
@@ -460,7 +504,7 @@ describe GeneratorTestResultsBacktrace do
|
|
|
460
504
|
|
|
461
505
|
describe '#do_gdb with brief Windows assert (no crash frame)' do
|
|
462
506
|
let(:filename_assert) { 'test_example_file_crash_assert.c' }
|
|
463
|
-
let(:test_cases_assert) { [{ test: 'test_add_numbers_triggers_assert', line_number: 20 }] }
|
|
507
|
+
let(:test_cases_assert) { [{ test: 'test_add_numbers_triggers_assert', symbol: 'test_add_numbers_triggers_assert', line_number: 20 }] }
|
|
464
508
|
let(:executable) { 'build/test/out/test_example_file_crash_assert/test_example_file_crash_assert.out' }
|
|
465
509
|
let(:shell_result) { { exit_code: 1, output: '', time: 0.0 } }
|
|
466
510
|
|
|
@@ -96,7 +96,7 @@ describe GeneratorTestRunner do
|
|
|
96
96
|
|
|
97
97
|
runner = build_runner( test_file_contents: source )
|
|
98
98
|
|
|
99
|
-
expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', line_number: 2 } ] )
|
|
99
|
+
expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', symbol: 'test_ShouldDoSomething', line_number: 2 } ] )
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
it 'remaps line numbers back to the original file when preprocessed content is given' do
|
|
@@ -114,7 +114,31 @@ describe GeneratorTestRunner do
|
|
|
114
114
|
|
|
115
115
|
runner = build_runner( test_file_contents: original, preprocessed_file_contents: preprocessed )
|
|
116
116
|
|
|
117
|
-
expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', line_number: 3 } ] )
|
|
117
|
+
expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', symbol: 'test_ShouldDoSomething', line_number: 3 } ] )
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'expands a parameterized test into one entry per TEST_CASE, carrying the runtime name and wrapper symbol' do
|
|
121
|
+
source = <<~SOURCE
|
|
122
|
+
void setUp(void) {}
|
|
123
|
+
void tearDown(void) {}
|
|
124
|
+
|
|
125
|
+
TEST_CASE(101, 1)
|
|
126
|
+
TEST_CASE(-1, 1)
|
|
127
|
+
void test_value_out_of_range_good(int a, int expected) {}
|
|
128
|
+
SOURCE
|
|
129
|
+
|
|
130
|
+
runner = described_class.new(
|
|
131
|
+
config: { use_param_tests: true },
|
|
132
|
+
test_file_contents: source,
|
|
133
|
+
parsing_parcels: @parsing_parcels
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
expect( runner.test_cases ).to eq(
|
|
137
|
+
[
|
|
138
|
+
{ test: 'test_value_out_of_range_good(101, 1)', symbol: 'runner_args1_test_value_out_of_range_good', line_number: 6 },
|
|
139
|
+
{ test: 'test_value_out_of_range_good(-1, 1)', symbol: 'runner_args2_test_value_out_of_range_good', line_number: 6 }
|
|
140
|
+
]
|
|
141
|
+
)
|
|
118
142
|
end
|
|
119
143
|
end
|
|
120
144
|
end
|
|
@@ -391,8 +391,8 @@ describe TestContextExtractor do
|
|
|
391
391
|
@extractor.collect_context( filepath, input, TestContextExtractor::Context::TEST_RUNNER_DETAILS )
|
|
392
392
|
|
|
393
393
|
expected = [
|
|
394
|
-
{:line_number => 2, :test => 'test_this_function'},
|
|
395
|
-
{:line_number => 12, :test => 'test_another_function'},
|
|
394
|
+
{:line_number => 2, :test => 'test_this_function', :symbol => 'test_this_function'},
|
|
395
|
+
{:line_number => 12, :test => 'test_another_function', :symbol => 'test_another_function'},
|
|
396
396
|
]
|
|
397
397
|
|
|
398
398
|
expect( @extractor.lookup_test_cases( filepath ) ).to eq expected
|
|
@@ -411,7 +411,7 @@ describe TestContextExtractor do
|
|
|
411
411
|
@extractor.collect_context( filepath, input, TestContextExtractor::Context::TEST_RUNNER_DETAILS )
|
|
412
412
|
}.not_to raise_error
|
|
413
413
|
|
|
414
|
-
expected = [{ :line_number => 2, :test => 'test_this_function' }]
|
|
414
|
+
expected = [{ :line_number => 2, :test => 'test_this_function', :symbol => 'test_this_function' }]
|
|
415
415
|
expect( @extractor.lookup_test_cases( filepath ) ).to eq expected
|
|
416
416
|
end
|
|
417
417
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ceedling
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mark VanderVoord
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2026-
|
|
13
|
+
date: 2026-08-06 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: rake
|
|
@@ -184,6 +184,7 @@ files:
|
|
|
184
184
|
- assets/test_example_file_boom.c
|
|
185
185
|
- assets/test_example_file_crash_assert.c
|
|
186
186
|
- assets/test_example_file_crash_sigsegv.c
|
|
187
|
+
- assets/test_example_file_crash_sigsegv_with_param.c
|
|
187
188
|
- assets/test_example_file_source_include.c
|
|
188
189
|
- assets/test_example_file_success.c
|
|
189
190
|
- assets/test_example_file_unity_printf.c
|