ceedling 1.1.1 → 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 +3 -3
- data/assets/broken_uncovered_file.c +13 -0
- data/assets/test_example_file_2.c +22 -0
- data/assets/test_example_file_crash_sigsegv_with_param.c +32 -0
- data/assets/tests_with_partials_coverage/src/blanks.c +10 -0
- data/assets/tests_with_partials_coverage/src/blanks.h +1 -0
- data/assets/tests_with_partials_coverage/src/counter.c +12 -0
- data/assets/tests_with_partials_coverage/src/counter.h +1 -0
- data/assets/tests_with_partials_coverage/src/decorators.c +11 -0
- data/assets/tests_with_partials_coverage/src/decorators.h +4 -0
- data/assets/tests_with_partials_coverage/test/test_blanks.c +8 -0
- data/assets/tests_with_partials_coverage/test/test_counter.c +9 -0
- data/assets/tests_with_partials_coverage/test/test_decorators.c +8 -0
- data/bin/actions_wrapper.rb +7 -6
- data/docs/Changelog.md +25 -3
- data/lib/ceedling/c_extractor/c_extractor_declarations.rb +3 -1
- data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +20 -7
- data/lib/ceedling/config/configurator.rb +7 -1
- data/lib/ceedling/defaults.rb +8 -2
- data/lib/ceedling/generators/generator_partials.rb +13 -3
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +136 -68
- data/lib/ceedling/generators/generator_test_runner.rb +20 -2
- data/lib/ceedling/objects.yml +1 -0
- data/lib/ceedling/preprocess/preprocessinator_file_assembler.rb +10 -2
- data/lib/ceedling/preprocess/preprocessinator_reconstructor.rb +23 -15
- 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/gcov_deployment_spec.rb +20 -6
- data/spec/system/support/common_test_cases.rb +90 -0
- data/spec/system/support/gcov_common_test_cases.rb +31 -45
- data/spec/system/support/gcov_partials_test_cases.rb +116 -0
- data/spec/units/c_extractor/c_extractor_declarations_spec.rb +128 -0
- data/spec/units/c_extractor/c_extractor_definitions_spec.rb +7 -0
- data/spec/units/c_extractor/c_extractor_integration_spec.rb +17 -0
- data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +46 -0
- data/spec/units/configurator_spec.rb +45 -0
- data/spec/units/generators/generator_partials_spec.rb +32 -6
- 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/preprocess/preprocessinator_file_assembler_spec.rb +96 -2
- data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +151 -1
- data/spec/units/test_context_extractor_spec.rb +3 -3
- metadata +16 -2
|
@@ -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/ceedling/objects.yml
CHANGED
|
@@ -217,7 +217,11 @@ class PreprocessinatorFileAssembler
|
|
|
217
217
|
|
|
218
218
|
# Write contents of final preprocessed file a line at a time
|
|
219
219
|
# ----------------------------------------------------------
|
|
220
|
-
|
|
220
|
+
# Binary mode: `contents` lines below come from an upstream preprocessed
|
|
221
|
+
# file and may already carry their own line endings. Windows text mode
|
|
222
|
+
# rewrites "\n" on write, which would alter a line ending already present
|
|
223
|
+
# in that content instead of passing it through unchanged.
|
|
224
|
+
@file_wrapper.open( preprocessed_filepath, 'wb' ) do |file|
|
|
221
225
|
# Add include guards and extra blank lines to beginning of file contents
|
|
222
226
|
file << "#ifndef #{guardname}\n"
|
|
223
227
|
file << "#define #{guardname}\n\n"
|
|
@@ -361,7 +365,11 @@ class PreprocessinatorFileAssembler
|
|
|
361
365
|
def assemble_preprocessed_code_file(filename:, preprocessed_filepath:, contents:, extras:, includes:)
|
|
362
366
|
# Write contents of final preprocessed file a line at a time
|
|
363
367
|
# ----------------------------------------------------------
|
|
364
|
-
|
|
368
|
+
# Binary mode: `contents` lines below come from an upstream preprocessed
|
|
369
|
+
# file and may already carry their own line endings. Windows text mode
|
|
370
|
+
# rewrites "\n" on write, which would alter a line ending already present
|
|
371
|
+
# in that content instead of passing it through unchanged.
|
|
372
|
+
@file_wrapper.open( preprocessed_filepath, 'wb' ) do |file|
|
|
365
373
|
# Reinsert #include statements into stripped down file
|
|
366
374
|
# Rely on Include object stringification for formatting of incudes
|
|
367
375
|
includes.each { |include| file << "#{include}\n" }
|
|
@@ -9,9 +9,9 @@ require 'ceedling/constants'
|
|
|
9
9
|
require 'ceedling/encodinator'
|
|
10
10
|
require 'ceedling/parsing_parcels'
|
|
11
11
|
|
|
12
|
-
class PreprocessinatorReconstructor
|
|
13
|
-
|
|
14
|
-
constructor :parsing_parcels
|
|
12
|
+
class PreprocessinatorReconstructor
|
|
13
|
+
|
|
14
|
+
constructor :parsing_parcels, :file_wrapper
|
|
15
15
|
|
|
16
16
|
##
|
|
17
17
|
## Preprocessor Expansion Output Handling
|
|
@@ -104,10 +104,13 @@ class PreprocessinatorReconstructor
|
|
|
104
104
|
# Opens `input_filepath` for reading and `output_filepath` for writing,
|
|
105
105
|
# then delegates to `compact_from_expansion` with the resulting IO objects.
|
|
106
106
|
def compact_file_from_expansion(input_filepath:, source_filepath:, output_filepath:)
|
|
107
|
-
#
|
|
108
|
-
# (localized markers
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
# Binary mode on both ends: GCC output under non-C locale contains non-ASCII
|
|
108
|
+
# bytes (localized markers; per-line clean_encoding in _scan_expansion_for_file
|
|
109
|
+
# handles content), and the compacted output is read again downstream by code
|
|
110
|
+
# that depends on its line count being exact. Windows text mode would rewrite
|
|
111
|
+
# "\n" to "\r\n" on write, which this content must not be subject to.
|
|
112
|
+
@file_wrapper.open( input_filepath, 'rb' ) do |input|
|
|
113
|
+
@file_wrapper.open( output_filepath, 'wb' ) do |output|
|
|
111
114
|
compact_from_expansion( input: input, filepath: source_filepath, output: output )
|
|
112
115
|
end
|
|
113
116
|
end
|
|
@@ -280,16 +283,21 @@ class PreprocessinatorReconstructor
|
|
|
280
283
|
|
|
281
284
|
# Buffer for the last logical line (may still receive aggregated content)
|
|
282
285
|
pending_line = nil
|
|
283
|
-
#
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
#
|
|
286
|
+
# Count of blank lines to emit after pending_line when flushed. Tracked as
|
|
287
|
+
# an exact count, not just "was there a blank" -- callers that depend on
|
|
288
|
+
# this method for line-for-line fidelity with the original source (so
|
|
289
|
+
# that unrelated line-number math downstream stays correct) need every
|
|
290
|
+
# blank line preserved, including runs that used to be a multi-line
|
|
291
|
+
# comment before an earlier pass turned it into equivalent blank lines.
|
|
292
|
+
pending_blank_count = 0
|
|
293
|
+
|
|
294
|
+
# Yields pending_line (and any pending blank lines) then clears the buffer
|
|
287
295
|
flush = lambda do
|
|
288
296
|
unless pending_line.nil?
|
|
289
297
|
block.call( pending_line )
|
|
290
|
-
block.call( '' )
|
|
298
|
+
pending_blank_count.times { block.call( '' ) }
|
|
291
299
|
pending_line = nil
|
|
292
|
-
|
|
300
|
+
pending_blank_count = 0
|
|
293
301
|
end
|
|
294
302
|
end
|
|
295
303
|
|
|
@@ -308,9 +316,9 @@ class PreprocessinatorReconstructor
|
|
|
308
316
|
# Strip a line so we can omit useless blank lines
|
|
309
317
|
_line = line.strip()
|
|
310
318
|
|
|
311
|
-
# Skip processing blank lines, but
|
|
319
|
+
# Skip processing blank lines, but track an exact count of them so runs of blanks survive intact
|
|
312
320
|
if _line.empty?
|
|
313
|
-
|
|
321
|
+
pending_blank_count += 1 if !pending_line.nil? && !pending_line.empty?
|
|
314
322
|
next
|
|
315
323
|
end
|
|
316
324
|
|
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
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
require 'spec_system_helper'
|
|
9
9
|
require_relative 'support/gcov_common_test_cases'
|
|
10
|
+
require_relative 'support/gcov_partials_test_cases'
|
|
10
11
|
|
|
11
12
|
ceedling_system_tests do
|
|
12
13
|
describe "Gcov" do
|
|
13
14
|
include CommonSystemTestCases
|
|
14
15
|
include GcovCommonTestCases
|
|
16
|
+
include GcovPartialsTestCases
|
|
15
17
|
before :all do
|
|
16
18
|
determine_reports_to_test
|
|
17
19
|
@c = SystemContext.new
|
|
@@ -50,6 +52,18 @@ ceedling_system_tests do
|
|
|
50
52
|
test_case :create_html_report_100_coverage_excluding_crashing_test_case
|
|
51
53
|
end
|
|
52
54
|
|
|
55
|
+
describe "Coverage reporting with Partials" do
|
|
56
|
+
before do
|
|
57
|
+
@c.with_context do
|
|
58
|
+
@c.ceedling_appcmd_exec("new --local #{@proj_name}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
test_case :gcov_partials_coverage_blank_lines_in_function_body
|
|
63
|
+
test_case :gcov_partials_coverage_decorators_on_own_lines
|
|
64
|
+
test_case :gcov_partials_coverage_function_scope_static_promotion
|
|
65
|
+
end
|
|
66
|
+
|
|
53
67
|
describe "Backtrace with GDB" do
|
|
54
68
|
include_context "requires gdb"
|
|
55
69
|
|
|
@@ -78,7 +92,7 @@ ceedling_system_tests do
|
|
|
78
92
|
# than resetting, crashing the test executable before Unity can print
|
|
79
93
|
# its statistics line.
|
|
80
94
|
FileUtils.rm_rf('temp_sensor')
|
|
81
|
-
output =
|
|
95
|
+
output = @c.ceedling_appcmd_exec("example temp_sensor")
|
|
82
96
|
expect(output).to match(/created/)
|
|
83
97
|
end
|
|
84
98
|
end
|
|
@@ -86,7 +100,7 @@ ceedling_system_tests do
|
|
|
86
100
|
it "should be testable" do
|
|
87
101
|
@c.with_context do
|
|
88
102
|
Dir.chdir "temp_sensor" do
|
|
89
|
-
@output =
|
|
103
|
+
@output = @c.ceedling_build_exec("gcov:all --mixin=add_gcov")
|
|
90
104
|
# Validate full test suite results
|
|
91
105
|
expect(@output).to match(/TESTED:\s+86/)
|
|
92
106
|
expect(@output).to match(/PASSED:\s+86/)
|
|
@@ -107,7 +121,7 @@ ceedling_system_tests do
|
|
|
107
121
|
it "should be able to test a single module (it should INHERIT file-specific flags)" do
|
|
108
122
|
@c.with_context do
|
|
109
123
|
Dir.chdir "temp_sensor" do
|
|
110
|
-
@output =
|
|
124
|
+
@output = @c.ceedling_build_exec("gcov:TemperatureCalculator --mixin=add_gcov")
|
|
111
125
|
expect(@output).to match(/TESTED:\s+2/)
|
|
112
126
|
expect(@output).to match(/PASSED:\s+2/)
|
|
113
127
|
|
|
@@ -119,7 +133,7 @@ ceedling_system_tests do
|
|
|
119
133
|
it "should be able to test multiple files matching a pattern" do
|
|
120
134
|
@c.with_context do
|
|
121
135
|
Dir.chdir "temp_sensor" do
|
|
122
|
-
@output =
|
|
136
|
+
@output = @c.ceedling_build_exec("gcov:pattern[Temp] --mixin=add_gcov")
|
|
123
137
|
expect(@output).to match(/TESTED:\s+6/)
|
|
124
138
|
expect(@output).to match(/PASSED:\s+6/)
|
|
125
139
|
|
|
@@ -132,7 +146,7 @@ ceedling_system_tests do
|
|
|
132
146
|
it "should be able to test all files matching in a path" do
|
|
133
147
|
@c.with_context do
|
|
134
148
|
Dir.chdir "temp_sensor" do
|
|
135
|
-
@output =
|
|
149
|
+
@output = @c.ceedling_build_exec("gcov:path[adc] --mixin=add_gcov")
|
|
136
150
|
expect(@output).to match(/TESTED:\s+24/)
|
|
137
151
|
expect(@output).to match(/PASSED:\s+24/)
|
|
138
152
|
|
|
@@ -147,7 +161,7 @@ ceedling_system_tests do
|
|
|
147
161
|
it "should be able to test specific test cases in a file" do
|
|
148
162
|
@c.with_context do
|
|
149
163
|
Dir.chdir "temp_sensor" do
|
|
150
|
-
@output =
|
|
164
|
+
@output = @c.ceedling_build_exec('gcov:path[adc] --mixin=add_gcov --test-case="RunShouldNot"')
|
|
151
165
|
expect(@output).to match(/TESTED:\s+2/)
|
|
152
166
|
expect(@output).to match(/PASSED:\s+2/)
|
|
153
167
|
|