ceedling 1.1.7 → 1.1.8
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/docs/Changelog.md +30 -0
- data/docs/KnownIssues.md +2 -0
- data/docs/mkdocs/plugins/gcov/gcovr.md +9 -4
- data/docs/mkdocs/reference/gcov-plugin.md +9 -4
- data/lib/ceedling/c_extractor/c_extractor_declarations.rb +11 -10
- data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +39 -2
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +5 -3
- data/lib/ceedling/generators/generator_test_runner.rb +5 -1
- data/lib/ceedling/preprocess/preprocessinator_line_marker_includes_extractor.rb +6 -1
- data/lib/ceedling/preprocess/preprocessinator_reconstructor.rb +18 -5
- data/lib/version.rb +1 -1
- data/plugins/gcov/lib/console_reportinator.rb +7 -0
- data/plugins/gcov/lib/gcov.rb +29 -10
- data/plugins/gcov/lib/gcovr_reportinator.rb +62 -48
- data/plugins/gcov/lib/reportgenerator_reportinator.rb +10 -6
- data/site-local/plugins/gcov/gcovr.html +9 -4
- data/site-local/reference/gcov-plugin.html +9 -4
- data/site-local/sitemap.xml.gz +0 -0
- data/spec/system/gcov_deployment_spec.rb +1 -0
- data/spec/system/support/gcov_common_test_cases.rb +18 -0
- data/spec/units/c_extractor/c_extractor_declarations_spec.rb +16 -0
- data/spec/units/c_extractor/c_extractor_integration_spec.rb +53 -0
- data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +45 -0
- data/spec/units/generators/generator_partials_spec.rb +98 -0
- data/spec/units/generators/generator_test_results_backtrace_spec.rb +26 -0
- data/spec/units/generators/generator_test_runner_spec.rb +23 -0
- data/spec/units/preprocess/preprocessinator_line_marker_includes_extractor_spec.rb +64 -0
- data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +74 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c1edc565bc9fcb24accc9fd0d5001076fe86564cb0acd02c7d1fc092476c2c1
|
|
4
|
+
data.tar.gz: 0c5ec0119bc3249824177c6094afd3e3ef9ee7da698b92fed894924d45d83625
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6efc0307567637d4019978131f45758d694b97ea93a4a7ada5ff6be8c682d70eff9d8d453de4882cc46344bb1b0aa098dbf7982582a7d7a168818caba2bd7e51
|
|
7
|
+
data.tar.gz: 6366fddeaff5572f317efffb161ff64378cae4e6b2dea30aa11f3baf4aac1777611331483d071a080cb9a01aa1e4087409d5314559881ae0a12ce753a1f86311
|
data/GIT_COMMIT_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
885dc91
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Ceedling 
|
|
2
2
|
========
|
|
3
3
|
|
|
4
|
-
**Ceedling 1.1.
|
|
4
|
+
**Ceedling 1.1.8** 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].
|
data/docs/Changelog.md
CHANGED
|
@@ -10,6 +10,36 @@ This changelog is complemented by three other documents:
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
# [1.1.8] — 2026-09-10
|
|
14
|
+
|
|
15
|
+
## 💪 Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed a crashed test case in a parameterized group occasionally being misattributed to a different, shorter-named test case in the same group whose name happened to be a substring of the one actually named in the crash backtrace.
|
|
18
|
+
- Fixed a test case's line number occasionally being misattributed to an unrelated helper function whose name merely contained the test's name as a substring, cascading incorrect line numbers to every test case after it in the same file.
|
|
19
|
+
|
|
20
|
+
### Partials
|
|
21
|
+
|
|
22
|
+
- [#1262](https://github.com/ThrowTheSwitch/Ceedling/issues/1262) Fixed a Partials-generated header occasionally concatenating two adjacent `#define` lines in one line causing a stray '#' compilation error. The triggering condition involved a `//` or `/* */` comment containing an apostrophe or quote (e.g. `// Don't ...`) that disrupted string literal handling.
|
|
23
|
+
|
|
24
|
+
### Preprocessing
|
|
25
|
+
|
|
26
|
+
- [#1266](https://github.com/ThrowTheSwitch/Ceedling/issues/1266) Fixed a reconstructed header silently dropping any macro whose name or value merely contained the header's own include guard as a substring (e.g. guard `RTC_H` matching inside `RTC_HOUR_SECONDS`), causing undeclared-identifier compile errors.
|
|
27
|
+
- [#1268](https://github.com/ThrowTheSwitch/Ceedling/issues/1268) Fixed a mockable header's `#include` losing the enums, structs, and function prototypes of a conditionally-included file when that `#include` was indented inside its `#ifdef` block, causing undeclared-identifier compile errors.
|
|
28
|
+
|
|
29
|
+
### Gcov plugin
|
|
30
|
+
|
|
31
|
+
- Fixed `:gcov ↳ :gcovr ↳ :decisions` reporting an incorrect minimum-version requirement; the version check (and docs) now correctly require `gcovr` 5.1 or higher.
|
|
32
|
+
- Fixed `:gcov ↳ :gcovr ↳ :fail_under_line`/`:fail_under_branch`/`:fail_under_decision`/`:fail_under_function` silently accepting `0`, contradicting the documented 1–100 range; `0` is now rejected.
|
|
33
|
+
- Fixed `:gcov ↳ :gcovr ↳ :object_directory` and `:source_encoding` values containing a space breaking the constructed `gcovr` command line; both are now quoted consistently with every other string-valued option.
|
|
34
|
+
- Fixed a regex-metacharacter-injection risk (e.g. a literal `.`) in `:gcov ↳ :gcovr`'s and `:gcov ↳ :report_generator`'s auto-generated exclusion patterns in `:test_file_prefix`, `:mock_prefix`, or the build root path could silently over- or under-match files.
|
|
35
|
+
- Fixed `:gcov ↳ :mcdc` triggering a `gcc --version` check (and a potential build-breaking exception for an older compiler) on every build in which a project enables the Gcov plugin, not only `gcov:` builds.
|
|
36
|
+
- Fixed only the first of multiple simultaneously violated `:gcov ↳ :gcovr ↳ :fail_under_*` thresholds being reported when a build-breaking exception is raised; every violated threshold is now listed.
|
|
37
|
+
- Fixed a latent mutation risk in `:gcov ↳ :report_generator` option handling that could accumulate stale exclusion patterns across repeated report-generation calls within a single run.
|
|
38
|
+
- `:gcov ↳ :gcovr ↳ :branches`/`:sort_uncovered`/`:sort_percentage` now use `gcovr`'s renamed `--txt-metric branch`/`--sort uncovered-number`/`--sort uncovered-percent` flags on `gcovr` 7.0+, silencing `gcovr`'s deprecation warnings for those options. The pre-7.0 flag names are still used automatically with older `gcovr`.
|
|
39
|
+
- Fixed the Gcov console summary silently producing no report and no log line at all for a Partial-implementation source whose `gcov` output couldn't be matched. It now logs the same "Found no coverage results" message the equivalent non-Partial case already did.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
13
43
|
# [1.1.7] — 2026-09-03
|
|
14
44
|
|
|
15
45
|
## 💪 Fixed
|
data/docs/KnownIssues.md
CHANGED
|
@@ -20,6 +20,8 @@ Known issues are complemented by three other documents:
|
|
|
20
20
|
1. While header file search paths are now customizable per executable, this currently only applies to the search paths the compiler uses. Distinguishing test files or header files of the same name in different directories for test runner and mock generation respectively continues to rely on educated guesses in Ceedling code.
|
|
21
21
|
1. All header files needed for test compilation must be within the `:includes` path collection. Relative paths in include directives that extend outside the path collection will cause build problems.
|
|
22
22
|
1. Any path for a C file specified with `TEST_SOURCE_FILE(...)` is in relation to **_project root_** — that is, from where you execute `ceedling` at the command line. If you move source files or change your directory structure, many of your `TEST_SOURCE_FILE(...)` calls may need to be updated. A more flexible and dynamic approach to path handling will come in a future update.
|
|
23
|
+
1. In certain combinations of conditional preprocessing blocks with dependent symbols defined in another file, Ceedling can silently fail to extract computed includes (e.g. `#include SOME_MACRO()`).
|
|
24
|
+
1. User includes (`#include "path/user.h"`) from source C files lose their relative path when Partial and mock header files are generated from source. Compilation failures can result from certain uncommon cases involving headers of the same names in different directories and a source file including both such headers.
|
|
23
25
|
1. The Bullseye code coverage plugin has been temporarily disabled as of 1.0.0. The makers of Bullseye have generously provided a license for development, and the plugin will be available in the next minor release.
|
|
24
26
|
|
|
25
27
|
---
|
|
@@ -128,7 +128,7 @@ Report the decision coverage. For HTML, JSON, and the summary report.
|
|
|
128
128
|
|
|
129
129
|
Implied automatically whenever `:fail_under_decision` is set.
|
|
130
130
|
|
|
131
|
-
**Requires:** `gcovr`
|
|
131
|
+
**Requires:** `gcovr` 5.1 or higher
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
@@ -163,21 +163,26 @@ Select the source file character encoding. (`gcovr --source-encoding`)
|
|
|
163
163
|
### `:branches`
|
|
164
164
|
|
|
165
165
|
Report the branch coverage instead of the line coverage. Applies to the text
|
|
166
|
-
report only.
|
|
166
|
+
report only. Uses `gcovr --txt-metric branch` on `gcovr` 7.0+ (`--branches` is
|
|
167
|
+
deprecated as of 7.0, though still functional); `gcovr --branches` below it.
|
|
167
168
|
|
|
168
169
|
---
|
|
169
170
|
|
|
170
171
|
### `:sort_uncovered`
|
|
171
172
|
|
|
172
173
|
Sort entries by increasing number of uncovered lines. Applies to text and HTML
|
|
173
|
-
reports.
|
|
174
|
+
reports. Uses `gcovr --sort uncovered-number` on `gcovr` 7.0+
|
|
175
|
+
(`--sort-uncovered` is deprecated as of 7.0, though still functional);
|
|
176
|
+
`gcovr --sort-uncovered` below it.
|
|
174
177
|
|
|
175
178
|
---
|
|
176
179
|
|
|
177
180
|
### `:sort_percentage`
|
|
178
181
|
|
|
179
182
|
Sort entries by increasing percentage of uncovered lines. Applies to text and
|
|
180
|
-
HTML reports.
|
|
183
|
+
HTML reports. Uses `gcovr --sort uncovered-percent` on `gcovr` 7.0+
|
|
184
|
+
(`--sort-percentage` is deprecated as of 7.0, though still functional);
|
|
185
|
+
`gcovr --sort-percentage` below it.
|
|
181
186
|
|
|
182
187
|
---
|
|
183
188
|
|
|
@@ -229,7 +229,7 @@ Report the decision coverage. For HTML, JSON, and the summary report.
|
|
|
229
229
|
|
|
230
230
|
Implied automatically whenever `:fail_under_decision` is set.
|
|
231
231
|
|
|
232
|
-
**Requires:** `gcovr`
|
|
232
|
+
**Requires:** `gcovr` 5.1 or higher
|
|
233
233
|
|
|
234
234
|
---
|
|
235
235
|
|
|
@@ -262,21 +262,26 @@ Source file character encoding. (`gcovr --source-encoding`)
|
|
|
262
262
|
### `:branches`
|
|
263
263
|
|
|
264
264
|
Report branch coverage instead of line coverage. Applies to text reports only.
|
|
265
|
-
|
|
265
|
+
Uses `gcovr --txt-metric branch` on `gcovr` 7.0+ (`--branches` is deprecated as
|
|
266
|
+
of 7.0, though still functional); `gcovr --branches` below it.
|
|
266
267
|
|
|
267
268
|
---
|
|
268
269
|
|
|
269
270
|
### `:sort_uncovered`
|
|
270
271
|
|
|
271
272
|
Sort report entries by increasing number of uncovered lines. Applies to text
|
|
272
|
-
and HTML reports.
|
|
273
|
+
and HTML reports. Uses `gcovr --sort uncovered-number` on `gcovr` 7.0+
|
|
274
|
+
(`--sort-uncovered` is deprecated as of 7.0, though still functional);
|
|
275
|
+
`gcovr --sort-uncovered` below it.
|
|
273
276
|
|
|
274
277
|
---
|
|
275
278
|
|
|
276
279
|
### `:sort_percentage`
|
|
277
280
|
|
|
278
281
|
Sort report entries by increasing percentage of uncovered lines. Applies to
|
|
279
|
-
text and HTML reports.
|
|
282
|
+
text and HTML reports. Uses `gcovr --sort uncovered-percent` on `gcovr` 7.0+
|
|
283
|
+
(`--sort-percentage` is deprecated as of 7.0, though still functional);
|
|
284
|
+
`gcovr --sort-percentage` below it.
|
|
280
285
|
|
|
281
286
|
---
|
|
282
287
|
|
|
@@ -402,16 +402,17 @@ class CExtractorDeclarations
|
|
|
402
402
|
return $1.strip
|
|
403
403
|
end
|
|
404
404
|
|
|
405
|
-
# Find the name in the text and take everything before it (including pointer specifiers)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
#
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
405
|
+
# Find the name in the text and take everything before it (including pointer specifiers).
|
|
406
|
+
# A plain rindex(name) would find the last substring occurrence of `name` anywhere in
|
|
407
|
+
# text, even inside an unrelated longer identifier that merely ends with it (e.g. `name`
|
|
408
|
+
# "count" inside a type-side identifier like "recount") -- \b anchors this to a whole
|
|
409
|
+
# identifier occurrence. Greedy `.*` in a single match (not scan) naturally finds the
|
|
410
|
+
# rightmost such occurrence, since it consumes as much as possible before backtracking
|
|
411
|
+
# just enough to let the trailing \bname\b still match.
|
|
412
|
+
match = text.match(/\A(.*)\b#{Regexp.escape(name)}\b/m)
|
|
413
|
+
return nil if match.nil?
|
|
414
|
+
|
|
415
|
+
type_part = match[1]
|
|
415
416
|
type_part.strip
|
|
416
417
|
end
|
|
417
418
|
|
|
@@ -241,9 +241,46 @@ class CExtractorPreprocessing
|
|
|
241
241
|
text = scanner.scan(/#/)
|
|
242
242
|
|
|
243
243
|
until scanner.eos?
|
|
244
|
-
|
|
244
|
+
# '/' is excluded here (alongside the quote/backslash/newline characters
|
|
245
|
+
# already excluded) so a comment marker is never swallowed into this plain
|
|
246
|
+
# literal run -- it has to be seen and dispatched on its own below, before
|
|
247
|
+
# any '"' or "'" inside the comment's own text gets mistaken for the start
|
|
248
|
+
# of a string/char literal (see the // and /* branches for why that matters).
|
|
249
|
+
text << (scanner.scan(/[^"'\\\n\/]*/) || '')
|
|
250
|
+
|
|
251
|
+
if scanner.scan(%r{//})
|
|
252
|
+
# A trailing line comment (e.g. "// don't change this") can contain an
|
|
253
|
+
# apostrophe or quote that isn't a literal delimiter at all. Consumed the
|
|
254
|
+
# ordinary way, that stray quote would send skip_c_string hunting for a
|
|
255
|
+
# closing match, straight through this directive's own newline and into
|
|
256
|
+
# whatever follows -- silently merging the next #define into this one.
|
|
257
|
+
# Comment content is captured verbatim and never scanned for literals.
|
|
258
|
+
#
|
|
259
|
+
# A '\' immediately before the physical newline still splices lines here,
|
|
260
|
+
# same as everywhere else in a directive (line splicing is a translation
|
|
261
|
+
# phase that happens before comments are stripped), so the comment -- and
|
|
262
|
+
# the directive -- keeps going onto the next physical line rather than
|
|
263
|
+
# ending at that newline.
|
|
264
|
+
before = scanner.pos - 2
|
|
265
|
+
loop do
|
|
266
|
+
scanner.scan(/[^\\\n]*/)
|
|
267
|
+
break if scanner.eos?
|
|
268
|
+
break unless scanner.scan(/\\\n/) || scanner.scan(/\\/)
|
|
269
|
+
end
|
|
270
|
+
text << scanner.string[before...scanner.pos]
|
|
271
|
+
|
|
272
|
+
elsif scanner.scan(%r{/\*})
|
|
273
|
+
# A block comment can legitimately span physical lines without ending the
|
|
274
|
+
# directive (only an un-commented newline does that) -- its content, quotes
|
|
275
|
+
# included, is likewise never scanned for literals, only for its own close.
|
|
276
|
+
before = scanner.pos - 2
|
|
277
|
+
scanner.skip_until(%r{\*/}) || scanner.terminate
|
|
278
|
+
text << scanner.string[before...scanner.pos]
|
|
279
|
+
|
|
280
|
+
elsif scanner.scan(%r{/})
|
|
281
|
+
text << '/'
|
|
245
282
|
|
|
246
|
-
|
|
283
|
+
elsif (ch = scanner.peek(1)) == '"' || ch == "'"
|
|
247
284
|
before = scanner.pos
|
|
248
285
|
@c_extractor_code_text.skip_c_string(scanner, ch)
|
|
249
286
|
text << scanner.string[before...scanner.pos]
|
|
@@ -95,9 +95,11 @@ class GeneratorTestResultsBacktrace
|
|
|
95
95
|
# Prefer whichever unresolved member's own C symbol is actually named in the gdb
|
|
96
96
|
# backtrace (works regardless of position in the group); fall back to the first
|
|
97
97
|
# unresolved member if no member's symbol can be found in the transcript (e.g. a
|
|
98
|
-
# brief crash report with no frame information at all).
|
|
98
|
+
# brief crash report with no frame information at all). \b anchors the symbol so a
|
|
99
|
+
# shorter unresolved symbol that's merely a suffix of a longer one actually named in
|
|
100
|
+
# the frame (e.g. "foo" vs "my_foo") can't steal the attribution.
|
|
99
101
|
crashed_case = unresolved.find do |tc|
|
|
100
|
-
crash_result[:output].match?(
|
|
102
|
+
crash_result[:output].match?( /\b#{Regexp.escape(tc[:symbol])}\s*\(\)\sat/ )
|
|
101
103
|
end
|
|
102
104
|
crashed_case ||= unresolved.first
|
|
103
105
|
|
|
@@ -458,7 +460,7 @@ class GeneratorTestResultsBacktrace
|
|
|
458
460
|
output.lines.filter_map do |line|
|
|
459
461
|
line = line.strip
|
|
460
462
|
next if line.empty?
|
|
461
|
-
next if line =~ /^#{filename}.+:(PASS|FAIL|IGNORE)/
|
|
463
|
+
next if line =~ /^#{Regexp.escape(filename)}.+:(PASS|FAIL|IGNORE)/
|
|
462
464
|
line
|
|
463
465
|
end
|
|
464
466
|
end
|
|
@@ -129,7 +129,11 @@ class GeneratorTestRunner
|
|
|
129
129
|
break if remaining.empty?
|
|
130
130
|
|
|
131
131
|
next_case = remaining.first
|
|
132
|
-
|
|
132
|
+
# Escaped (a raw test name can contain regex metacharacters) and \b-anchored on both
|
|
133
|
+
# sides so an unrelated line that merely contains this test's name as a substring --
|
|
134
|
+
# e.g. a helper function like `reset_test_ab_state()` sitting before `test_ab` itself
|
|
135
|
+
# -- can't steal its line number and cascade misalignment to every case after it.
|
|
136
|
+
if (line =~ /\b#{Regexp.escape(next_case[:test])}\b/)
|
|
133
137
|
next_case[:line_number] = line_num
|
|
134
138
|
remaining.shift
|
|
135
139
|
end
|
|
@@ -82,7 +82,12 @@ require 'set'
|
|
|
82
82
|
|
|
83
83
|
# Parse GCC preprocessor output (from -fdirectives-only) to extract system include directives
|
|
84
84
|
class PreprocessinatorLineMarkerIncludesExtractor
|
|
85
|
-
|
|
85
|
+
# Leading whitespace is tolerated (`^\s*`, not just `^`): GCC's -fdirectives-only output
|
|
86
|
+
# replaces a top-level #include with a line marker that inherits that #include's own
|
|
87
|
+
# original indentation (GH #1268) -- an indented `#include "x.h"` produces an indented
|
|
88
|
+
# ` # 1 "x.h" 1`, not the flush-left marker every OTHER marker GCC generates uses.
|
|
89
|
+
# Without this, such an include is silently missing from the extracted list entirely.
|
|
90
|
+
LINE_MARKER_REGEX = /^\s*#\s+(\d+)\s+"([^"]+)"(?:\s+(\d+(?:\s+\d+)*))?$/ unless const_defined?(:LINE_MARKER_REGEX)
|
|
86
91
|
|
|
87
92
|
SYSTEM = :system unless const_defined?(:SYSTEM)
|
|
88
93
|
USER = :user unless const_defined?(:USER)
|
|
@@ -236,8 +236,15 @@ class PreprocessinatorReconstructor
|
|
|
236
236
|
def extract_macro_defs(file_contents, include_guard)
|
|
237
237
|
macro_definitions = extract_multiline_directives( file_contents, 'define' )
|
|
238
238
|
|
|
239
|
-
# Remove an include guard if provided
|
|
240
|
-
|
|
239
|
+
# Remove an include guard if provided. A plain substring test here would also reject
|
|
240
|
+
# any ordinary macro whose name or value happens to contain the guard string -- e.g.
|
|
241
|
+
# guard RTC_H matching inside RTC_HOUR_SECONDS' own name, or inside RTC_DAY_SECONDS'
|
|
242
|
+
# value, which references RTC_HOUR_SECONDS (GH #1266). Anchoring to "#define <guard>"
|
|
243
|
+
# followed by whitespace or end-of-line only matches the guard definition itself.
|
|
244
|
+
if !include_guard.nil?
|
|
245
|
+
include_guard_regex = /^\s*#\s*define\s+#{Regexp.escape(include_guard)}(?:\s|$)/
|
|
246
|
+
macro_definitions.reject! {|macro| macro =~ include_guard_regex }
|
|
247
|
+
end
|
|
241
248
|
|
|
242
249
|
return macro_definitions
|
|
243
250
|
end
|
|
@@ -271,10 +278,16 @@ class PreprocessinatorReconstructor
|
|
|
271
278
|
def _scan_expansion_for_file(input, filepath, &block)
|
|
272
279
|
# Expand filepath under inspection to ensure proper match
|
|
273
280
|
extraction_filepath = File.expand_path( filepath )
|
|
274
|
-
# Preprocessor directive blocks generally take the form of '# <digits> <text> [optional digits]'
|
|
275
|
-
|
|
281
|
+
# Preprocessor directive blocks generally take the form of '# <digits> <text> [optional digits]'.
|
|
282
|
+
# Leading whitespace is tolerated (`^\s*`, not just `^`): GCC's -fdirectives-only output
|
|
283
|
+
# replaces a top-level #include with a line marker that inherits that #include's own
|
|
284
|
+
# original indentation (GH #1268) -- an indented `#include "x.h"` produces an indented
|
|
285
|
+
# ` # 1 "x.h" 1`, not the flush-left marker every OTHER marker GCC generates uses.
|
|
286
|
+
# Failing to recognize such a marker here leaves `extract` stuck at whatever it already
|
|
287
|
+
# was instead of correctly toggling off, leaking the ignored file's content into output.
|
|
288
|
+
directive = /^\s*# \d+ \"/
|
|
276
289
|
# Line markers have the specific form of '# <digits> "path/filename.ext" [optional digits]' (see above)
|
|
277
|
-
line_marker =
|
|
290
|
+
line_marker = /^\s*#\s(\d+)\s\"(.+)\"/
|
|
278
291
|
# Boolean to ping pong between line-by-line extract/ignore
|
|
279
292
|
extract = false
|
|
280
293
|
|
data/lib/version.rb
CHANGED
|
@@ -169,6 +169,13 @@ class ConsoleReportinator < GcovReportinator
|
|
|
169
169
|
section = section.reject { |line| line.include?( File.basename( source,'.*') ) }
|
|
170
170
|
report = section.map { |line| report_name + ' | ' + line }.join('')
|
|
171
171
|
@loginator.log( report )
|
|
172
|
+
else
|
|
173
|
+
# A Partial whose remapped name still doesn't match any File header in gcov's
|
|
174
|
+
# output -- without this, a Partial's unparseable coverage silently produces no
|
|
175
|
+
# report and no log at all, unlike the equivalent non-Partial case just below.
|
|
176
|
+
@loginator.lazy( Verbosity::COMPLAIN ) do
|
|
177
|
+
"Found no coverage results for #{test}::#{File.basename(source)}"
|
|
178
|
+
end
|
|
172
179
|
end
|
|
173
180
|
|
|
174
181
|
# Otherwise, found no coverage results
|
data/plugins/gcov/lib/gcov.rb
CHANGED
|
@@ -70,16 +70,12 @@ class Gcov < Plugin
|
|
|
70
70
|
|
|
71
71
|
@mutex = Mutex.new()
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"(found #{gcc_version.major}.#{gcc_version.minor})"
|
|
80
|
-
)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
73
|
+
# See validate_mcdc_gcc_version! below for why this isn't checked eagerly here --
|
|
74
|
+
# setup() runs for every build this plugin is merely *enabled* for, not only real
|
|
75
|
+
# gcov: builds. Shelling out to `gcc --version` here would make GCC-version
|
|
76
|
+
# validation (and a build-breaking exception on too-old a GCC) fire even for builds
|
|
77
|
+
# that never touch :mcdc at all, e.g. plain `test:all`.
|
|
78
|
+
@mcdc_gcc_checked = false
|
|
83
79
|
end
|
|
84
80
|
|
|
85
81
|
# Called within class and also externally by plugin Rakefile
|
|
@@ -179,6 +175,8 @@ class Gcov < Plugin
|
|
|
179
175
|
|
|
180
176
|
# Compile all non-assembly files with coverage; gcovr --exclude filters non-production files from reports
|
|
181
177
|
if File.extname(source) != EXTENSION_ASSEMBLY
|
|
178
|
+
validate_mcdc_gcc_version!
|
|
179
|
+
|
|
182
180
|
arg_hash[:tool] = TOOLS_GCOV_COMPILER
|
|
183
181
|
arg_hash[:msg] = @reportinator.generate_module_progress(
|
|
184
182
|
operation: "Compiling with coverage",
|
|
@@ -192,6 +190,8 @@ class Gcov < Plugin
|
|
|
192
190
|
|
|
193
191
|
def pre_link_execute(arg_hash)
|
|
194
192
|
if arg_hash[:context] == GCOV_SYM
|
|
193
|
+
validate_mcdc_gcc_version!
|
|
194
|
+
|
|
195
195
|
@cli_gcov_task = true
|
|
196
196
|
arg_hash[:tool] = TOOLS_GCOV_LINKER
|
|
197
197
|
arg_hash[:flags] += ['-fcondition-coverage'] if @project_config[:gcov_mcdc]
|
|
@@ -364,6 +364,25 @@ class Gcov < Plugin
|
|
|
364
364
|
return reportinators
|
|
365
365
|
end
|
|
366
366
|
|
|
367
|
+
# Lazily validates :mcdc against the installed GCC version -- called from every
|
|
368
|
+
# gcov-context build hook that could otherwise emit -fcondition-coverage (compile,
|
|
369
|
+
# link), memoized so `gcc --version` runs at most once regardless of how many source
|
|
370
|
+
# files or hooks fire before it. See setup()'s own comment for why this isn't checked
|
|
371
|
+
# eagerly there.
|
|
372
|
+
def validate_mcdc_gcc_version!
|
|
373
|
+
return if @mcdc_gcc_checked
|
|
374
|
+
@mcdc_gcc_checked = true
|
|
375
|
+
return unless @project_config[:gcov_mcdc]
|
|
376
|
+
|
|
377
|
+
gcc_version = get_gcc_version()
|
|
378
|
+
if gcc_version.major < 14
|
|
379
|
+
raise CeedlingException.new(
|
|
380
|
+
":gcov ↳ :mcdc ➡️ Modified condition/decision coverage requires GCC 14 or higher " \
|
|
381
|
+
"(found #{gcc_version.major}.#{gcc_version.minor})"
|
|
382
|
+
)
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
367
386
|
def get_gcc_version()
|
|
368
387
|
command = @tool_executor.build_command_line( TOOLS_GCOV_GCC_VERSION, [])
|
|
369
388
|
|
|
@@ -66,9 +66,12 @@ class GcovrReportinator < GcovReportinator
|
|
|
66
66
|
)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
# --decisions was introduced in gcovr 5.1, not 6.0 -- confirmed directly against
|
|
70
|
+
# gcovr's own changelog and a real gcovr 5.1/5.0 install (5.1 accepts --decisions
|
|
71
|
+
# and proceeds; 5.0 rejects it outright as unrecognized).
|
|
72
|
+
if gcovr_opts[:decisions] && !min_version?( @gcovr_version, 5, 1 )
|
|
70
73
|
raise CeedlingException.new(
|
|
71
|
-
":gcov ↳ :gcovr ↳ :decisions ➡️ requires gcovr
|
|
74
|
+
":gcov ↳ :gcovr ↳ :decisions ➡️ requires gcovr 5.1 or higher " \
|
|
72
75
|
"(found #{@gcovr_version.major}.#{@gcovr_version.minor})"
|
|
73
76
|
)
|
|
74
77
|
end
|
|
@@ -116,9 +119,18 @@ class GcovrReportinator < GcovReportinator
|
|
|
116
119
|
args += "--gcov-filter \"#{gcovr_opts[:gcov_filter]}\" " unless gcovr_opts[:gcov_filter].nil?
|
|
117
120
|
args += "--gcov-exclude \"#{gcovr_opts[:gcov_exclude]}\" " unless gcovr_opts[:gcov_exclude].nil?
|
|
118
121
|
args += "--exclude-directories \"#{gcovr_opts[:exclude_directories]}\" " unless gcovr_opts[:exclude_directories].nil?
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
# gcovr 7.0 deprecated --branches/--sort-uncovered/--sort-percentage in favor of
|
|
123
|
+
# these renamed equivalents (still functional, but warn, below 7.0); use whichever
|
|
124
|
+
# form the detected gcovr_version actually expects.
|
|
125
|
+
if gcovr_opts[:branches]
|
|
126
|
+
args += (min_version?(gcovr_version, 7, 0) ? "--txt-metric branch " : "--branches ")
|
|
127
|
+
end
|
|
128
|
+
if gcovr_opts[:sort_uncovered]
|
|
129
|
+
args += (min_version?(gcovr_version, 7, 0) ? "--sort uncovered-number " : "--sort-uncovered ")
|
|
130
|
+
end
|
|
131
|
+
if gcovr_opts[:sort_percentage]
|
|
132
|
+
args += (min_version?(gcovr_version, 7, 0) ? "--sort uncovered-percent " : "--sort-percentage ")
|
|
133
|
+
end
|
|
122
134
|
args += "--print-summary " if gcovr_opts[:print_summary]
|
|
123
135
|
args += "--gcov-executable \"#{gcovr_opts[:gcov_executable]}\" " unless gcovr_opts[:gcov_executable].nil?
|
|
124
136
|
args += "--exclude-unreachable-branches " if gcovr_opts[:exclude_unreachable_branches]
|
|
@@ -152,18 +164,24 @@ class GcovrReportinator < GcovReportinator
|
|
|
152
164
|
next if gcovr_opts[opt].nil?
|
|
153
165
|
|
|
154
166
|
value = gcovr_opts[opt]
|
|
155
|
-
|
|
167
|
+
|
|
156
168
|
# Value sanity checks for :fail_under_* settings
|
|
157
169
|
if opt.to_s =~ /fail_/
|
|
158
170
|
if not value.is_a? Integer
|
|
159
171
|
raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} ➡️ '#{value}' must be an integer")
|
|
160
|
-
elsif (value <
|
|
161
|
-
raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} ➡️ '#{value}' must be an integer percentage
|
|
172
|
+
elsif (value < 1) || (value > 100)
|
|
173
|
+
raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} ➡️ '#{value}' must be an integer percentage 1 – 100")
|
|
162
174
|
end
|
|
163
175
|
end
|
|
164
|
-
|
|
165
|
-
#
|
|
166
|
-
|
|
176
|
+
|
|
177
|
+
# :source_encoding/:object_directory are quoted (unlike the already-validated,
|
|
178
|
+
# always-integer :fail_under_* values) so a value containing a space doesn't
|
|
179
|
+
# break the constructed command line.
|
|
180
|
+
if [:source_encoding, :object_directory].include?(opt)
|
|
181
|
+
args += "--#{opt.to_s.gsub('_','-')} \"#{value}\" " unless value.nil?
|
|
182
|
+
else
|
|
183
|
+
args += "--#{opt.to_s.gsub('_','-')} #{value} " unless value.nil?
|
|
184
|
+
end
|
|
167
185
|
end
|
|
168
186
|
|
|
169
187
|
return args
|
|
@@ -409,7 +427,12 @@ class GcovrReportinator < GcovReportinator
|
|
|
409
427
|
_opts[:report_exclude] = excludes unless excludes.empty?
|
|
410
428
|
end
|
|
411
429
|
|
|
412
|
-
|
|
430
|
+
# No :mcdc-specific gcovr flag to set here -- GCC's own condition/MC-DC data flows
|
|
431
|
+
# into gcovr's reports unconditionally, in every format, regardless of --decisions
|
|
432
|
+
# (confirmed directly: gcovr's Condition-labeled HTML/JSON content is byte-identical
|
|
433
|
+
# with and without --decisions; that flag only adds its own separate, additive
|
|
434
|
+
# Decision data alongside it). :mcdc's own gcovr-version floor is still enforced in
|
|
435
|
+
# initialize -- there's just nothing more for gcovr's own CLI args to do here.
|
|
413
436
|
|
|
414
437
|
return _opts
|
|
415
438
|
end
|
|
@@ -417,22 +440,29 @@ class GcovrReportinator < GcovReportinator
|
|
|
417
440
|
|
|
418
441
|
# Build a combined Python regex for gcovr's `--exclude` flag covering all
|
|
419
442
|
# non-production file categories: test files, support files, and generated/framework files.
|
|
443
|
+
# Path/prefix values are config-driven, so they're Regexp-escaped -- interpolated raw, a
|
|
444
|
+
# metacharacter (e.g. a literal '.') in a project's own path would silently over- or
|
|
445
|
+
# under-match.
|
|
420
446
|
def build_report_exclusions
|
|
421
447
|
data = build_exclusion_data
|
|
422
448
|
patterns = []
|
|
423
449
|
|
|
450
|
+
test_prefix = Regexp.escape(data[:test_prefix].to_s)
|
|
451
|
+
build_root = Regexp.escape(data[:build_root].to_s)
|
|
452
|
+
src_extension = Regexp.escape(data[:src_extension].to_s)
|
|
453
|
+
|
|
424
454
|
data[:test_paths].each do |path|
|
|
425
455
|
# Test files (e.g. test_foo.c)
|
|
426
|
-
patterns << ".*#{path}.*/#{
|
|
456
|
+
patterns << ".*#{Regexp.escape(path)}.*/#{test_prefix}.+\\#{src_extension}$"
|
|
427
457
|
end
|
|
428
458
|
|
|
429
459
|
# Support files (e.g. helpers, stubs, fixtures) — never production source
|
|
430
460
|
data[:support_paths].each do |path|
|
|
431
|
-
patterns << ".*#{path}/.+\\#{
|
|
461
|
+
patterns << ".*#{Regexp.escape(path)}/.+\\#{src_extension}$"
|
|
432
462
|
end
|
|
433
463
|
|
|
434
464
|
# Any generated files for tests or vendored framework C source files below the root of the build directory
|
|
435
|
-
patterns << ".*#{
|
|
465
|
+
patterns << ".*#{build_root}/.+\\#{EXTENSION_CORE_SOURCE}$"
|
|
436
466
|
|
|
437
467
|
return patterns
|
|
438
468
|
end
|
|
@@ -535,59 +565,43 @@ class GcovrReportinator < GcovReportinator
|
|
|
535
565
|
|
|
536
566
|
# Output to console a human-friendly message on certain coverage failure exit codes.
|
|
537
567
|
# Prefers the actual gcovr error text from stderr; falls back to descriptive strings.
|
|
538
|
-
#
|
|
568
|
+
# gcovr's composite exit code bit-ORs every violated :fail_under_* threshold together --
|
|
569
|
+
# every violated bit is collected and reported, not just whichever is checked first.
|
|
539
570
|
def gcovr_exec_exception?(opts, exitcode, boom, shell_result=nil)
|
|
571
|
+
violations = []
|
|
540
572
|
|
|
541
573
|
# Special handling of exit code 2 with --fail-under-line
|
|
542
574
|
if ((exitcode & 2) == 2) and !opts[:fail_under_line].nil?
|
|
543
575
|
fallback = "Line coverage is less than the configured minimum of #{opts[:fail_under_line]}%"
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
raise CeedlingException.new(msg)
|
|
547
|
-
else
|
|
548
|
-
@loginator.log( msg, Verbosity::COMPLAIN )
|
|
549
|
-
# Clear bit in exit code
|
|
550
|
-
exitcode &= ~2
|
|
551
|
-
end
|
|
576
|
+
violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
|
|
577
|
+
exitcode &= ~2
|
|
552
578
|
end
|
|
553
579
|
|
|
554
580
|
# Special handling of exit code 4 with --fail-under-branch
|
|
555
581
|
if ((exitcode & 4) == 4) and !opts[:fail_under_branch].nil?
|
|
556
582
|
fallback = "Branch coverage is less than the configured minimum of #{opts[:fail_under_branch]}%"
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
raise CeedlingException.new(msg)
|
|
560
|
-
else
|
|
561
|
-
@loginator.log( msg, Verbosity::COMPLAIN )
|
|
562
|
-
# Clear bit in exit code
|
|
563
|
-
exitcode &= ~4
|
|
564
|
-
end
|
|
583
|
+
violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
|
|
584
|
+
exitcode &= ~4
|
|
565
585
|
end
|
|
566
586
|
|
|
567
587
|
# Special handling of exit code 8 with --fail-under-decision
|
|
568
588
|
if ((exitcode & 8) == 8) and !opts[:fail_under_decision].nil?
|
|
569
589
|
fallback = "Decision coverage is less than the configured minimum of #{opts[:fail_under_decision]}%"
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
raise CeedlingException.new(msg)
|
|
573
|
-
else
|
|
574
|
-
@loginator.log( msg, Verbosity::COMPLAIN )
|
|
575
|
-
# Clear bit in exit code
|
|
576
|
-
exitcode &= ~8
|
|
577
|
-
end
|
|
590
|
+
violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
|
|
591
|
+
exitcode &= ~8
|
|
578
592
|
end
|
|
579
593
|
|
|
580
594
|
# Special handling of exit code 16 with --fail-under-function
|
|
581
595
|
if ((exitcode & 16) == 16) and !opts[:fail_under_function].nil?
|
|
582
596
|
fallback = "Function coverage is less than the configured minimum of #{opts[:fail_under_function]}%"
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
597
|
+
violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
|
|
598
|
+
exitcode &= ~16
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
if boom
|
|
602
|
+
raise CeedlingException.new( violations.join("\n") ) unless violations.empty?
|
|
603
|
+
else
|
|
604
|
+
violations.each { |msg| @loginator.log( msg, Verbosity::COMPLAIN ) }
|
|
591
605
|
end
|
|
592
606
|
|
|
593
607
|
# A non-zero exit code is a problem
|