ceedling 1.1.6 → 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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +2 -0
  5. data/README.md +4 -1
  6. data/docs/Changelog.md +41 -0
  7. data/docs/KnownIssues.md +3 -0
  8. data/docs/mkdocs/configuration/project-file.md +25 -7
  9. data/docs/mkdocs/configuration/reference/cexception.md +1 -1
  10. data/docs/mkdocs/configuration/reference/cmock.md +1 -1
  11. data/docs/mkdocs/configuration/reference/unity.md +1 -1
  12. data/docs/mkdocs/getting-started/quick-start.md +11 -8
  13. data/docs/mkdocs/plugins/gcov/gcovr.md +329 -189
  14. data/docs/mkdocs/plugins/gcov/reportgenerator.md +113 -31
  15. data/docs/mkdocs/reference/gcov-plugin.md +29 -6
  16. data/docs/mkdocs/snapshot/plugins/gcov/config/defaults.yml +0 -1
  17. data/lib/ceedling/c_extractor/c_extractor_declarations.rb +11 -10
  18. data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +39 -2
  19. data/lib/ceedling/generators/generator_test_results_backtrace.rb +21 -11
  20. data/lib/ceedling/generators/generator_test_runner.rb +5 -1
  21. data/lib/ceedling/plugins/plugin_reportinator.rb +9 -0
  22. data/lib/ceedling/plugins/report_tests_stdout_plugin.rb +1 -1
  23. data/lib/ceedling/preprocess/preprocessinator_line_marker_includes_extractor.rb +6 -1
  24. data/lib/ceedling/preprocess/preprocessinator_reconstructor.rb +18 -5
  25. data/lib/version.rb +1 -1
  26. data/plugins/gcov/config/defaults.yml +0 -1
  27. data/plugins/gcov/lib/console_reportinator.rb +7 -0
  28. data/plugins/gcov/lib/gcov.rb +64 -26
  29. data/plugins/gcov/lib/gcovr_reportinator.rb +91 -47
  30. data/plugins/gcov/lib/reportgenerator_reportinator.rb +10 -6
  31. data/plugins/valgrind/lib/valgrind.rb +1 -1
  32. data/site-local/configuration/project-file.html +78 -8
  33. data/site-local/configuration/reference/cexception.html +5 -5
  34. data/site-local/configuration/reference/cmock.html +5 -5
  35. data/site-local/configuration/reference/unity.html +5 -5
  36. data/site-local/getting-started/quick-start.html +13 -9
  37. data/site-local/plugins/gcov/gcovr.html +1220 -255
  38. data/site-local/plugins/gcov/reportgenerator.html +404 -46
  39. data/site-local/reference/gcov-plugin.html +48 -10
  40. data/site-local/sitemap.xml.gz +0 -0
  41. data/site-local/snapshot/plugins/gcov/config/defaults.yml +0 -1
  42. data/spec/support/system/system_context.rb +7 -3
  43. data/spec/system/deployment_as_gem_spec.rb +2 -0
  44. data/spec/system/deployment_as_vendor_spec.rb +2 -0
  45. data/spec/system/gcov_deployment_spec.rb +3 -0
  46. data/spec/system/support/common_test_cases.rb +42 -0
  47. data/spec/system/support/gcov_common_test_cases.rb +70 -0
  48. data/spec/units/c_extractor/c_extractor_declarations_spec.rb +16 -0
  49. data/spec/units/c_extractor/c_extractor_integration_spec.rb +53 -0
  50. data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +45 -0
  51. data/spec/units/generators/generator_partials_spec.rb +98 -0
  52. data/spec/units/generators/generator_test_results_backtrace_spec.rb +77 -0
  53. data/spec/units/generators/generator_test_runner_spec.rb +23 -0
  54. data/spec/units/plugin_reportinator_spec.rb +38 -0
  55. data/spec/units/preprocess/preprocessinator_line_marker_includes_extractor_spec.rb +64 -0
  56. data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +74 -0
  57. metadata +6 -2
@@ -4,55 +4,137 @@ The `ReportGenerator` utility may be configured with the following configuration
4
4
 
5
5
  All generated reports are found in `<build root>/artifacts/gcov/ReportGenerator/`.
6
6
 
7
+ ## Example configuration
8
+
7
9
  ```yaml
8
10
  :gcov:
9
11
  :report_generator:
10
- # Optional directory for storing persistent coverage information.
11
- # Can be used in future reports to show coverage evolution.
12
- :history_directory: <path>
12
+ :history_directory: build/artifacts/gcov/history
13
+ :file_filters: "-./vendor/*;-./build/*;-./test/*;+./src/*"
14
+ :verbosity: Warning
15
+ :tag: "release-1.4.0"
16
+ :num_parallel_threads: 4
17
+ :gcov_exclude:
18
+ - some_excluded_file
19
+ :custom_args:
20
+ - "-title:MyProject"
21
+ ```
22
+
23
+ ## Plugin configuration
24
+
25
+ ### `:history_directory`
26
+
27
+ Optional directory for storing persistent coverage information. Can be used
28
+ in future reports to show coverage evolution.
29
+
30
+ ---
31
+
32
+ ### `:plugins`
33
+
34
+ Optional plugin files for custom reports or custom history storage (separated
35
+ by semicolon).
36
+
37
+ **Example:** `plugin.dll;*.dll`
38
+
39
+ ---
40
+
41
+ ### `:assembly_filters`
42
+
43
+ Optional list of assemblies that should be included or excluded in the
44
+ report (separated by semicolon). Exclusion filters take precedence over
45
+ inclusion filters. Wildcards are allowed, but not regular expressions.
46
+
47
+ **Example:** `+<included>;-<excluded>`
48
+
49
+ ---
50
+
51
+ ### `:class_filters`
52
+
53
+ Optional list of classes that should be included or excluded in the report
54
+ (separated by semicolon). Exclusion filters take precedence over inclusion
55
+ filters. Wildcards are allowed, but not regular expressions.
56
+
57
+ **Example:** `+<included>;-<excluded>`
58
+
59
+ ---
60
+
61
+ ### `:file_filters`
62
+
63
+ Optional list of files that should be included or excluded in the report
64
+ (separated by semicolon). Exclusion filters take precedence over inclusion
65
+ filters. Wildcards are allowed, but not regular expressions. Ceedling places
66
+ your own patterns first, ahead of the exclusions it generates automatically
67
+ for test paths, the build root, and (when Partials are in use) Partial
68
+ source files, so your patterns take precedence.
13
69
 
14
- # Optional plugin files for custom reports or custom history storage (separated by semicolon).
15
- :plugins: <plugin.dll>;<*.dll>
70
+ **Example:** `"-./vendor/*;-./build/*;-./test/*;-./lib/*;+./src/*"`
16
71
 
17
- # Optional list of assemblies that should be included or excluded in the report (separated by semicolon).
18
- # Exclusion filters take precedence over inclusion filters.
19
- # Wildcards are allowed, but not regular expressions.
20
- :assembly_filters: +<included>;-<excluded>
72
+ ---
21
73
 
22
- # Optional list of classes that should be included or excluded in the report (separated by semicolon).
23
- # Exclusion filters take precedence over inclusion filters.
24
- # Wildcards are allowed, but not regular expressions.
25
- :class_filters: +<included>;-<excluded>
74
+ ### `:verbosity`
26
75
 
27
- # Optional list of files that should be included or excluded in the report (separated by semicolon).
28
- # Exclusion filters take precedence over inclusion filters.
29
- # Wildcards are allowed, but not regular expressions.
30
- # Example: "-./vendor/*;-./build/*;-./test/*;-./lib/*;+./src/*"
31
- :file_filters: +<included>;-<excluded>
76
+ The verbosity level of the log messages.
32
77
 
33
- # The verbosity level of the log messages.
34
- # Values: Verbose, Info, Warning, Error, Off (defaults to Warning)
35
- :verbosity: <level>
78
+ **Values:** `Verbose`, `Info`, `Warning`, `Error`, `Off`
36
79
 
37
- # Optional tag or build version.
38
- :tag: <tag>
80
+ **Default:** `Warning`
39
81
 
40
- # Optional list of one or more regular expressions to exclude gcov notes files that match these filters.
82
+ ---
83
+
84
+ ### `:tag`
85
+
86
+ Optional tag or build version.
87
+
88
+ ---
89
+
90
+ ### `:gcov_exclude`
91
+
92
+ Optional list of one or more regular expressions to exclude gcov notes
93
+ (`.gcno`) files that match these filters from coverage processing — these
94
+ files are never run through `gcov` at all. A trailing `.gcov` or `.gcno`
95
+ suffix on a pattern is stripped automatically, so either form works.
96
+
97
+ ```yaml
98
+ :gcov:
99
+ :report_generator:
41
100
  :gcov_exclude:
42
101
  - <regex>
43
102
  - ...
103
+ ```
104
+
105
+ **Default:** `[]`
44
106
 
45
- # Optionally set the number of threads to use in parallel. Defaults to 1.
46
- :threads: <count>
107
+ ---
47
108
 
48
- # Optional list of one or more command line arguments to pass to Report Generator.
49
- # Useful for configuring Risk Hotspots and Other Settings.
50
- # https://github.com/danielpalme/ReportGenerator/wiki/Settings
51
- # Note: This can be accomplished with Ceedling's tool configuration options outside of plugin
52
- # configuration but is supported here to collect configuration options in one place.
109
+ ### `:num_parallel_threads`
110
+
111
+ Optionally set the number of threads ReportGenerator uses in parallel. Drives
112
+ both of ReportGenerator's own `numberOfReportsParsedInParallel` and
113
+ `numberOfReportsMergedInParallel` settings together.
114
+
115
+ **Default:** unset (ReportGenerator's own default applies)
116
+
117
+ ---
118
+
119
+ ### `:custom_args`
120
+
121
+ Optional list of one or more command line arguments to pass to Report
122
+ Generator. Useful for configuring Risk Hotspots and other settings not
123
+ covered by the options above. See the
124
+ [ReportGenerator settings wiki](https://github.com/danielpalme/ReportGenerator/wiki/Settings).
125
+
126
+ Note: This can be accomplished with Ceedling's tool configuration options
127
+ outside of plugin configuration but is supported here to collect
128
+ configuration options in one place.
129
+
130
+ ```yaml
131
+ :gcov:
132
+ :report_generator:
53
133
  :custom_args:
54
134
  - <argument>
55
135
  - ...
56
136
  ```
57
137
 
138
+ **Default:** `[]`
139
+
58
140
  <br/><br/>
@@ -213,8 +213,24 @@ Exit with status 4 if total branch coverage is below this percentage.
213
213
  Exit with status 8 if total decision coverage is below this percentage.
214
214
  (`gcovr --fail-under-decision`)
215
215
 
216
+ `gcovr` treats `--fail-under-decision` as a no-op without `--decisions` also
217
+ present — setting this option implies `:decisions` (below) automatically.
218
+
216
219
  **Values:** `1`–`100`
217
220
 
221
+ **Requires:** `gcovr` 7.0 or higher
222
+
223
+ ---
224
+
225
+ ### `:decisions`
226
+
227
+ Report the decision coverage. For HTML, JSON, and the summary report.
228
+ (`gcovr --decisions`)
229
+
230
+ Implied automatically whenever `:fail_under_decision` is set.
231
+
232
+ **Requires:** `gcovr` 5.1 or higher
233
+
218
234
  ---
219
235
 
220
236
  ### `:fail_under_function`
@@ -246,21 +262,26 @@ Source file character encoding. (`gcovr --source-encoding`)
246
262
  ### `:branches`
247
263
 
248
264
  Report branch coverage instead of line coverage. Applies to text reports only.
249
- (`gcovr --branches`)
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.
250
267
 
251
268
  ---
252
269
 
253
270
  ### `:sort_uncovered`
254
271
 
255
272
  Sort report entries by increasing number of uncovered lines. Applies to text
256
- and HTML reports. (`gcovr --sort-uncovered`)
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.
257
276
 
258
277
  ---
259
278
 
260
279
  ### `:sort_percentage`
261
280
 
262
281
  Sort report entries by increasing percentage of uncovered lines. Applies to
263
- text and HTML reports. (`gcovr --sort-percentage`)
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.
264
285
 
265
286
  ---
266
287
 
@@ -552,11 +573,13 @@ are excluded from report generation.
552
573
 
553
574
  ---
554
575
 
555
- ### `:threads`
576
+ ### `:num_parallel_threads`
556
577
 
557
- Number of parallel threads to use during report generation.
578
+ Number of parallel threads to use during report generation. Drives both
579
+ ReportGenerator's own `numberOfReportsParsedInParallel` and
580
+ `numberOfReportsMergedInParallel` settings together.
558
581
 
559
- **Default:** `1`
582
+ **Default:** unset (ReportGenerator's own default applies)
560
583
 
561
584
  ---
562
585
 
@@ -27,7 +27,6 @@
27
27
 
28
28
  :report_generator:
29
29
  :verbosity: Warning # Default verbosity
30
- :collection_paths_source: [] # Explicitly defined as default empty array to simplify option validation code
31
30
  :custom_args: [] # Explicitly defined as default empty array to simplify option validation code
32
31
  :gcov_exclude: [] # Explicitly defined as default empty array to simplify option validation code
33
32
  ...
@@ -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
- idx = text.rindex(name)
407
- return nil if idx.nil?
408
-
409
- type_part = text[0...idx]
410
- # Include any pointer specifiers attached to the name
411
- if text[idx..] =~ /^#{Regexp.escape(name)}/
412
- # Look for * immediately before name (with optional space)
413
- type_part = type_part.rstrip
414
- end
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
- text << (scanner.scan(/[^"'\\\n]*/) || '')
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
- if (ch = scanner.peek(1)) == '"' || ch == "'"
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]
@@ -66,13 +66,17 @@ class GeneratorTestResultsBacktrace
66
66
  # Attribute each group member its own real result line, if Unity printed one
67
67
  group.each do |test_case|
68
68
  case crash_result[:output]
69
- # Success test case
70
- when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:PASS\s*$)/
69
+ # Success test case -- tolerates a trailing duration suffix (e.g. `PASS (12 ms)`,
70
+ # UNITY_INCLUDE_EXEC_TIME), matching the real format generator_test_results.rb's
71
+ # own parser already recognizes for it.
72
+ when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:PASS(\s\(.*ms\))?\s*$)/
71
73
  group_results[:passed] += 1
72
74
  group_results[:output] << $1
73
75
 
74
- # Ignored test case
75
- when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:IGNORE\s*$)/
76
+ # Ignored test case -- tolerates a trailing message (TEST_IGNORE_MESSAGE), same as
77
+ # FAIL below. Before this fix, a message-carrying ignore fell through to "unresolved"
78
+ # and was misreported as crash evidence -- IGNORE was only recognized bare.
79
+ when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:IGNORE(:.+)?\s*$)/
76
80
  group_results[:ignored] += 1
77
81
  group_results[:output] << $1
78
82
 
@@ -91,9 +95,11 @@ class GeneratorTestResultsBacktrace
91
95
  # Prefer whichever unresolved member's own C symbol is actually named in the gdb
92
96
  # backtrace (works regardless of position in the group); fall back to the first
93
97
  # unresolved member if no member's symbol can be found in the transcript (e.g. a
94
- # 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.
95
101
  crashed_case = unresolved.find do |tc|
96
- crash_result[:output].match?( /#{Regexp.escape(tc[:symbol])}\s*\(\)\sat/ )
102
+ crash_result[:output].match?( /\b#{Regexp.escape(tc[:symbol])}\s*\(\)\sat/ )
97
103
  end
98
104
  crashed_case ||= unresolved.first
99
105
 
@@ -260,13 +266,17 @@ class GeneratorTestResultsBacktrace
260
266
  # Attribute each group member its own real result line, if Unity printed one
261
267
  group.each do |test_case|
262
268
  case crash_result[:output]
263
- # Success test case
264
- when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:PASS\s*$)/
269
+ # Success test case -- tolerates a trailing duration suffix (e.g. `PASS (12 ms)`,
270
+ # UNITY_INCLUDE_EXEC_TIME), matching the real format generator_test_results.rb's
271
+ # own parser already recognizes for it.
272
+ when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:PASS(\s\(.*ms\))?\s*$)/
265
273
  group_results[:passed] += 1
266
274
  group_results[:output] << $1
267
275
 
268
- # Ignored test case
269
- when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:IGNORE\s*$)/
276
+ # Ignored test case -- tolerates a trailing message (TEST_IGNORE_MESSAGE), same as
277
+ # FAIL below. Before this fix, a message-carrying ignore fell through to "unresolved"
278
+ # and was misreported as crash evidence -- IGNORE was only recognized bare.
279
+ when /(^#{Regexp.escape(filename)}:\d+:#{Regexp.escape(test_case[:test])}:IGNORE(:.+)?\s*$)/
270
280
  group_results[:ignored] += 1
271
281
  group_results[:output] << $1
272
282
 
@@ -450,7 +460,7 @@ class GeneratorTestResultsBacktrace
450
460
  output.lines.filter_map do |line|
451
461
  line = line.strip
452
462
  next if line.empty?
453
- next if line =~ /^#{filename}.+:(PASS|FAIL|IGNORE)/
463
+ next if line =~ /^#{Regexp.escape(filename)}.+:(PASS|FAIL|IGNORE)/
454
464
  line
455
465
  end
456
466
  end
@@ -129,7 +129,11 @@ class GeneratorTestRunner
129
129
  break if remaining.empty?
130
130
 
131
131
  next_case = remaining.first
132
- if (line =~ /#{next_case[:test]}/)
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
@@ -95,6 +95,15 @@ class PluginReportinator
95
95
  return aggregated_results
96
96
  end
97
97
 
98
+ # A build's own test results -- pass or fail -- are core information a user
99
+ # silencing routine build chatter still wants to see; ERRORS is the floor
100
+ # for both, not just for a failing run. Centralized here so every plugin
101
+ # printing post-build test results (report_tests_stdout_plugin, gcov,
102
+ # valgrind) agrees on the same answer instead of each hardcoding its own.
103
+ def test_results_floor_verbosity
104
+ Verbosity::ERRORS
105
+ end
106
+
98
107
  def run_test_results_report(hash, verbosity=Verbosity::NORMAL)
99
108
  if @test_results_template.nil?
100
109
  raise CeedlingException.new( "No test results report template has been set." )
@@ -50,7 +50,7 @@ class ReportTestsStdoutPlugin < Plugin
50
50
 
51
51
  results = @plugin_reportinator.assemble_test_results( @result_list )
52
52
  hash = { :context => TEST_SYM, :results => results }
53
- verbosity = (results[:counts][:failed] > 0) ? Verbosity::ERRORS : Verbosity::NORMAL
53
+ verbosity = @plugin_reportinator.test_results_floor_verbosity
54
54
 
55
55
  # Print unit test suite results
56
56
  @plugin_reportinator.run_test_results_report( hash, verbosity )
@@ -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
- LINE_MARKER_REGEX = /^#\s+(\d+)\s+"([^"]+)"(?:\s+(\d+(?:\s+\d+)*))?$/ unless const_defined?(:LINE_MARKER_REGEX)
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
- macro_definitions.reject! {|macro| macro.include?( include_guard ) } if !include_guard.nil?
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
- directive = /^# \d+ \"/
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 = /^#\s(\d+)\s\"(.+)\"/
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
@@ -15,7 +15,7 @@
15
15
  module Ceedling
16
16
  module Version
17
17
  # Convenience constants for gem building, etc.
18
- GEM = '1.1.6'
18
+ GEM = '1.1.8'
19
19
  TAG = GEM
20
20
 
21
21
  # If run as a script print Ceedling's version to $stdout
@@ -27,7 +27,6 @@
27
27
 
28
28
  :report_generator:
29
29
  :verbosity: Warning # Default verbosity
30
- :collection_paths_source: [] # Explicitly defined as default empty array to simplify option validation code
31
30
  :custom_args: [] # Explicitly defined as default empty array to simplify option validation code
32
31
  :gcov_exclude: [] # Explicitly defined as default empty array to simplify option validation code
33
32
  ...
@@ -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