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
@@ -40,11 +40,19 @@ class Gcov < Plugin
40
40
  )
41
41
  end
42
42
 
43
- # Validate configuration and tools while building Reportinators
44
- @reportinators = build_reportinators(
45
- @project_config[:gcov_utilities],
46
- @reports_enabled
47
- )
43
+ # #1252 -- Validate `:gcov :utilities` config (cheap, no external tool needed) and
44
+ # build the console summary Reportinator (needs no external tool of its own) here,
45
+ # eagerly, since `setup()` runs for every build this plugin is merely *enabled* for,
46
+ # not only real `gcov:` builds. Deliberately NOT building the gcovr/ReportGenerator
47
+ # Reportinators here -- doing so validates gcovr/ReportGenerator are installed
48
+ # (see their own constructors), which would make either a hard dependency of any
49
+ # build at all (e.g. plain `test:all`) rather than only a real `gcov:` build. See
50
+ # generate_coverage_reports, which builds (and so validates) them lazily, only once
51
+ # post_build already knows a gcov: task actually ran.
52
+ validate_utilities_config( @project_config[:gcov_utilities] )
53
+ @console_reportinator = summaries_enabled?( @project_config ) ?
54
+ ConsoleReportinator.new( @ceedling, @project_config ) : nil
55
+ @reportinators = nil
48
56
 
49
57
  # Convenient instance variable references
50
58
  @configurator = @ceedling[:configurator]
@@ -62,16 +70,12 @@ class Gcov < Plugin
62
70
 
63
71
  @mutex = Mutex.new()
64
72
 
65
- # Validate MC/DC configuration against GCC version (only incurs gcc --version when :mcdc: TRUE)
66
- if @project_config[:gcov_mcdc]
67
- gcc_version = get_gcc_version()
68
- if gcc_version.major < 14
69
- raise CeedlingException.new(
70
- ":gcov :mcdc ➡️ Modified condition/decision coverage requires GCC 14 or higher " \
71
- "(found #{gcc_version.major}.#{gcc_version.minor})"
72
- )
73
- end
74
- 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
75
79
  end
76
80
 
77
81
  # Called within class and also externally by plugin Rakefile
@@ -171,6 +175,8 @@ class Gcov < Plugin
171
175
 
172
176
  # Compile all non-assembly files with coverage; gcovr --exclude filters non-production files from reports
173
177
  if File.extname(source) != EXTENSION_ASSEMBLY
178
+ validate_mcdc_gcc_version!
179
+
174
180
  arg_hash[:tool] = TOOLS_GCOV_COMPILER
175
181
  arg_hash[:msg] = @reportinator.generate_module_progress(
176
182
  operation: "Compiling with coverage",
@@ -184,6 +190,8 @@ class Gcov < Plugin
184
190
 
185
191
  def pre_link_execute(arg_hash)
186
192
  if arg_hash[:context] == GCOV_SYM
193
+ validate_mcdc_gcc_version!
194
+
187
195
  @cli_gcov_task = true
188
196
  arg_hash[:tool] = TOOLS_GCOV_LINKER
189
197
  arg_hash[:flags] += ['-fcondition-coverage'] if @project_config[:gcov_mcdc]
@@ -214,7 +222,7 @@ class Gcov < Plugin
214
222
  results: results
215
223
  }
216
224
 
217
- verbosity = (results[:counts][:failed] > 0) ? Verbosity::ERRORS : Verbosity::NORMAL
225
+ verbosity = @plugin_reportinator.test_results_floor_verbosity
218
226
 
219
227
  # Print unit test suite results
220
228
  @plugin_reportinator.run_test_results_report( hash, verbosity )
@@ -257,6 +265,15 @@ class Gcov < Plugin
257
265
  def generate_coverage_reports()
258
266
  return if not @reports_enabled
259
267
 
268
+ # #1252 -- Lazily build (and so validate the external tools of) the gcovr/
269
+ # ReportGenerator Reportinators only now: this method only runs from post_build
270
+ # (automatic reporting) or the standalone `gcov:report` task (manual reporting,
271
+ # mutually exclusive with automatic per :gcov_report_task) -- either way, only
272
+ # once a real gcov: task is confirmed to have already run. See setup()'s own
273
+ # comment for why this can't happen any earlier. `||=` just guards against ever
274
+ # rebuilding (and so re-validating) on a hypothetical repeat call.
275
+ @reportinators ||= build_reportinators( @project_config[:gcov_utilities] )
276
+
260
277
  @reportinators.each do |reportinator|
261
278
  # Create the artifacts output directory.
262
279
  @file_wrapper.mkdir( reportinator.artifacts_path ) if reportinator.artifacts_path
@@ -312,16 +329,10 @@ class Gcov < Plugin
312
329
  return sources - tested_sources
313
330
  end
314
331
 
315
- def build_reportinators(config, enabled)
316
- reportinators = []
317
-
318
- # Instantiate console summary reportinator if summaries enabled
319
- @console_reportinator = summaries_enabled?(@project_config) ?
320
- ConsoleReportinator.new(@ceedling, @project_config) : nil
321
-
322
- # Do not instantiate file reportinators (and tool validation) unless reports enabled
323
- return reportinators if (!enabled)
324
-
332
+ # Fail fast at plugin setup if :utilities holds an unrecognized name (e.g. a typo) --
333
+ # cheap, config-shape-only validation, so this stays eager even though the actual
334
+ # Reportinators it names are built lazily (see build_reportinators, generate_coverage_reports).
335
+ def validate_utilities_config(config)
325
336
  config.each do |reportinator|
326
337
  if not GCOV_UTILITY_NAMES.map(&:upcase).include?( reportinator.upcase )
327
338
  options = GCOV_UTILITY_NAMES.map{ |utility| "'#{utility}'" }.join(', ')
@@ -329,6 +340,14 @@ class Gcov < Plugin
329
340
  raise CeedlingException.new(msg)
330
341
  end
331
342
  end
343
+ end
344
+
345
+ # #1252 -- Deliberately not called from setup(); see its own comment and
346
+ # generate_coverage_reports, the only caller. Constructing GcovrReportinator/
347
+ # ReportGeneratorReportinator validates gcovr/ReportGenerator are installed, so this
348
+ # can only run once a real gcov: task is confirmed to have run.
349
+ def build_reportinators(config)
350
+ reportinators = []
332
351
 
333
352
  # Run reports using gcovr
334
353
  if utility_enabled?( config, GCOV_UTILITY_NAME_GCOVR )
@@ -345,6 +364,25 @@ class Gcov < Plugin
345
364
  return reportinators
346
365
  end
347
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
+
348
386
  def get_gcc_version()
349
387
  command = @tool_executor.build_command_line( TOOLS_GCOV_GCC_VERSION, [])
350
388
 
@@ -50,6 +50,31 @@ class GcovrReportinator < GcovReportinator
50
50
  "(found #{@gcovr_version.major}.#{@gcovr_version.minor})"
51
51
  )
52
52
  end
53
+
54
+ # --decisions requires gcovr 6+; --fail-under-decision (which depends on --decisions)
55
+ # is a distinct, later addition requiring gcovr 7+ -- confirmed directly against real
56
+ # gcovr 6.0, which rejects --fail-under-decision outright as an unrecognized argument.
57
+ # Raised here, upfront, rather than left to surface as gcovr's own opaque CLI error or
58
+ # silently dropped -- either of which would leave a configured coverage threshold
59
+ # quietly never actually checked.
60
+ gcovr_opts = @config[GCOVR_SETTING_PREFIX.to_sym] || {}
61
+
62
+ if !gcovr_opts[:fail_under_decision].nil? && !min_version?( @gcovr_version, 7, 0 )
63
+ raise CeedlingException.new(
64
+ ":gcov ↳ :gcovr ↳ :fail_under_decision ➡️ requires gcovr 7 or higher " \
65
+ "(found #{@gcovr_version.major}.#{@gcovr_version.minor})"
66
+ )
67
+ end
68
+
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 )
73
+ raise CeedlingException.new(
74
+ ":gcov ↳ :gcovr ↳ :decisions ➡️ requires gcovr 5.1 or higher " \
75
+ "(found #{@gcovr_version.major}.#{@gcovr_version.minor})"
76
+ )
77
+ end
53
78
  end
54
79
 
55
80
  # Generate the gcovr report(s) specified in the options.
@@ -94,9 +119,18 @@ class GcovrReportinator < GcovReportinator
94
119
  args += "--gcov-filter \"#{gcovr_opts[:gcov_filter]}\" " unless gcovr_opts[:gcov_filter].nil?
95
120
  args += "--gcov-exclude \"#{gcovr_opts[:gcov_exclude]}\" " unless gcovr_opts[:gcov_exclude].nil?
96
121
  args += "--exclude-directories \"#{gcovr_opts[:exclude_directories]}\" " unless gcovr_opts[:exclude_directories].nil?
97
- args += "--branches " if gcovr_opts[:branches]
98
- args += "--sort-uncovered " if gcovr_opts[:sort_uncovered]
99
- args += "--sort-percentage " if gcovr_opts[:sort_percentage]
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
100
134
  args += "--print-summary " if gcovr_opts[:print_summary]
101
135
  args += "--gcov-executable \"#{gcovr_opts[:gcov_executable]}\" " unless gcovr_opts[:gcov_executable].nil?
102
136
  args += "--exclude-unreachable-branches " if gcovr_opts[:exclude_unreachable_branches]
@@ -112,6 +146,14 @@ class GcovrReportinator < GcovReportinator
112
146
  args += "--merge-mode-functions \"#{gcovr_opts[:merge_mode_function]}\" " unless gcovr_opts[:merge_mode_function].nil?
113
147
  end
114
148
 
149
+ # --fail-under-decision is a no-op to gcovr without --decisions also present (that's
150
+ # what actually turns decision-coverage analysis on) -- :fail_under_decision implies
151
+ # it automatically so the threshold option can never be configured into silently
152
+ # failing outright. initialize already guarantees gcovr 6+ whenever either is set
153
+ # (7+ specifically whenever :fail_under_decision is set), matching this method's own
154
+ # 6+ gate above.
155
+ args += "--decisions " if gcovr_opts[:decisions] || !gcovr_opts[:fail_under_decision].nil?
156
+
115
157
  [:fail_under_line,
116
158
  :fail_under_branch,
117
159
  :fail_under_decision,
@@ -122,18 +164,24 @@ class GcovrReportinator < GcovReportinator
122
164
  next if gcovr_opts[opt].nil?
123
165
 
124
166
  value = gcovr_opts[opt]
125
-
167
+
126
168
  # Value sanity checks for :fail_under_* settings
127
169
  if opt.to_s =~ /fail_/
128
170
  if not value.is_a? Integer
129
171
  raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} ➡️ '#{value}' must be an integer")
130
- elsif (value < 0) || (value > 100)
131
- raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} ➡️ '#{value}' must be an integer percentage 0 – 100")
172
+ elsif (value < 1) || (value > 100)
173
+ raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} ➡️ '#{value}' must be an integer percentage 1 – 100")
132
174
  end
133
175
  end
134
-
135
- # If the YAML key has a value, trasnform key into command line argument with value and concatenate
136
- args += "--#{opt.to_s.gsub('_','-')} #{value} " unless value.nil?
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
137
185
  end
138
186
 
139
187
  return args
@@ -327,7 +375,7 @@ class GcovrReportinator < GcovReportinator
327
375
  # configuration must come from the gcovr configuration file itself.
328
376
  IGNORED_WHEN_CONFIG_FILE_SET = [
329
377
  :report_include, :report_exclude, :gcov_filter, :gcov_exclude, :exclude_directories,
330
- :branches, :sort_uncovered, :sort_percentage, :print_summary, :gcov_executable,
378
+ :branches, :decisions, :sort_uncovered, :sort_percentage, :print_summary, :gcov_executable,
331
379
  :exclude_unreachable_branches, :exclude_throw_branches, :use_gcov_files, :gcov_ignore_parse_errors,
332
380
  :keep, :delete, :threads, :merge_mode_function,
333
381
  :fail_under_line, :fail_under_branch, :fail_under_decision, :fail_under_function,
@@ -379,7 +427,12 @@ class GcovrReportinator < GcovReportinator
379
427
  _opts[:report_exclude] = excludes unless excludes.empty?
380
428
  end
381
429
 
382
- _opts[:mcdc] = true if opts[:gcov_mcdc]
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.
383
436
 
384
437
  return _opts
385
438
  end
@@ -387,22 +440,29 @@ class GcovrReportinator < GcovReportinator
387
440
 
388
441
  # Build a combined Python regex for gcovr's `--exclude` flag covering all
389
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.
390
446
  def build_report_exclusions
391
447
  data = build_exclusion_data
392
448
  patterns = []
393
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
+
394
454
  data[:test_paths].each do |path|
395
455
  # Test files (e.g. test_foo.c)
396
- patterns << ".*#{path}.*/#{data[:test_prefix]}.+\\#{data[:src_extension]}$"
456
+ patterns << ".*#{Regexp.escape(path)}.*/#{test_prefix}.+\\#{src_extension}$"
397
457
  end
398
458
 
399
459
  # Support files (e.g. helpers, stubs, fixtures) — never production source
400
460
  data[:support_paths].each do |path|
401
- patterns << ".*#{path}/.+\\#{data[:src_extension]}$"
461
+ patterns << ".*#{Regexp.escape(path)}/.+\\#{src_extension}$"
402
462
  end
403
463
 
404
464
  # Any generated files for tests or vendored framework C source files below the root of the build directory
405
- patterns << ".*#{data[:build_root]}/.+\\#{EXTENSION_CORE_SOURCE}$"
465
+ patterns << ".*#{build_root}/.+\\#{EXTENSION_CORE_SOURCE}$"
406
466
 
407
467
  return patterns
408
468
  end
@@ -505,59 +565,43 @@ class GcovrReportinator < GcovReportinator
505
565
 
506
566
  # Output to console a human-friendly message on certain coverage failure exit codes.
507
567
  # Prefers the actual gcovr error text from stderr; falls back to descriptive strings.
508
- # Perform the logic on whether to raise an exception
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.
509
570
  def gcovr_exec_exception?(opts, exitcode, boom, shell_result=nil)
571
+ violations = []
510
572
 
511
573
  # Special handling of exit code 2 with --fail-under-line
512
574
  if ((exitcode & 2) == 2) and !opts[:fail_under_line].nil?
513
575
  fallback = "Line coverage is less than the configured minimum of #{opts[:fail_under_line]}%"
514
- msg = "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
515
- if boom
516
- raise CeedlingException.new(msg)
517
- else
518
- @loginator.log( msg, Verbosity::COMPLAIN )
519
- # Clear bit in exit code
520
- exitcode &= ~2
521
- end
576
+ violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
577
+ exitcode &= ~2
522
578
  end
523
579
 
524
580
  # Special handling of exit code 4 with --fail-under-branch
525
581
  if ((exitcode & 4) == 4) and !opts[:fail_under_branch].nil?
526
582
  fallback = "Branch coverage is less than the configured minimum of #{opts[:fail_under_branch]}%"
527
- msg = "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
528
- if boom
529
- raise CeedlingException.new(msg)
530
- else
531
- @loginator.log( msg, Verbosity::COMPLAIN )
532
- # Clear bit in exit code
533
- exitcode &= ~4
534
- end
583
+ violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
584
+ exitcode &= ~4
535
585
  end
536
586
 
537
587
  # Special handling of exit code 8 with --fail-under-decision
538
588
  if ((exitcode & 8) == 8) and !opts[:fail_under_decision].nil?
539
589
  fallback = "Decision coverage is less than the configured minimum of #{opts[:fail_under_decision]}%"
540
- msg = "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
541
- if boom
542
- raise CeedlingException.new(msg)
543
- else
544
- @loginator.log( msg, Verbosity::COMPLAIN )
545
- # Clear bit in exit code
546
- exitcode &= ~8
547
- end
590
+ violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
591
+ exitcode &= ~8
548
592
  end
549
593
 
550
594
  # Special handling of exit code 16 with --fail-under-function
551
595
  if ((exitcode & 16) == 16) and !opts[:fail_under_function].nil?
552
596
  fallback = "Function coverage is less than the configured minimum of #{opts[:fail_under_function]}%"
553
- msg = "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
554
- if boom
555
- raise CeedlingException.new(msg)
556
- else
557
- @loginator.log( msg, Verbosity::COMPLAIN )
558
- # Clear bit in exit code
559
- exitcode &= ~16
560
- end
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 ) }
561
605
  end
562
606
 
563
607
  # A non-zero exit code is a problem
@@ -128,7 +128,9 @@ class ReportGeneratorReportinator < GcovReportinator
128
128
 
129
129
  # Get the ReportGenerator options from the project options.
130
130
  def collect_reportgenerator_opts(opts)
131
- _opts = opts[REPORT_GENERATOR_SETTING_PREFIX.to_sym]
131
+ # dup prevents repeated calls from accumulating mutations on the shared opts hash --
132
+ # mirrors GcovrReportinator#collect_gcovr_opts' identical guard and rationale.
133
+ _opts = opts[REPORT_GENERATOR_SETTING_PREFIX.to_sym].dup
132
134
 
133
135
  # Auto-generate -filefilters: exclusion patterns for test paths and the build root.
134
136
  # These exclude test files and all generated/framework files from coverage reports.
@@ -275,17 +277,19 @@ class ReportGeneratorReportinator < GcovReportinator
275
277
  # Build filename-fragment patterns for the .gcno scan exclusion regex.
276
278
  # Excludes test files, generated mocks, and test runners from gcov processing.
277
279
  # The '.*' suffix handles source extensions embedded in .gcno filenames (e.g. test_foo.c.gcno).
280
+ # test_prefix/mock_prefix are config-driven, so they're Regexp-escaped -- unescaped, a
281
+ # metacharacter in either (e.g. a literal '.') would silently over- or under-match.
278
282
  def build_gcno_exclusions
279
283
  data = build_exclusion_data
280
284
  # Use [^\/\\]* (no path separators) rather than .* to match filenames only.
281
285
  # Ceedling names build output dirs after test files (e.g. build/gcov/out/test_foo/),
282
286
  # so .* would greedily match test_foo/bar.c and exclude ALL production sources.
283
287
  [
284
- "#{data[:test_prefix]}[^\\/\\\\]*", # test_foo.gcno
285
- "#{data[:mock_prefix]}[^\\/\\\\]*", # MockBar.gcno
286
- "[^\\/\\\\]*_runner[^\\/\\\\]*", # test_foo_runner.gcno
287
- File.basename(UNITY_C_FILE, '.*'), # unity.gcno
288
- File.basename(CMOCK_C_FILE, '.*') # cmock.gcno
288
+ "#{Regexp.escape(data[:test_prefix].to_s)}[^\\/\\\\]*", # test_foo.gcno
289
+ "#{Regexp.escape(data[:mock_prefix].to_s)}[^\\/\\\\]*", # MockBar.gcno
290
+ "[^\\/\\\\]*_runner[^\\/\\\\]*", # test_foo_runner.gcno
291
+ Regexp.escape(File.basename(UNITY_C_FILE, '.*')), # unity.gcno
292
+ Regexp.escape(File.basename(CMOCK_C_FILE, '.*')) # cmock.gcno
289
293
  ]
290
294
  end
291
295
 
@@ -101,7 +101,7 @@ class Valgrind < Plugin
101
101
  results: results
102
102
  }
103
103
 
104
- verbosity = (results[:counts][:failed] > 0) ? Verbosity::ERRORS : Verbosity::NORMAL
104
+ verbosity = @plugin_reportinator.test_results_floor_verbosity
105
105
 
106
106
  # Print unit test suite results
107
107
  @plugin_reportinator.run_test_results_report( hash, verbosity )
@@ -1684,6 +1684,34 @@
1684
1684
  </span>
1685
1685
  </a>
1686
1686
 
1687
+ <nav class="md-nav" aria-label="Some YAML Learnin’">
1688
+ <ul class="md-nav__list">
1689
+
1690
+ <li class="md-nav__item">
1691
+ <a href="#yaml-highlights-overview" class="md-nav__link">
1692
+ <span class="md-ellipsis">
1693
+
1694
+ YAML highlights &amp; overview
1695
+
1696
+ </span>
1697
+ </a>
1698
+
1699
+ </li>
1700
+
1701
+ <li class="md-nav__item">
1702
+ <a href="#yaml-anchors-aliases-and-extensions" class="md-nav__link">
1703
+ <span class="md-ellipsis">
1704
+
1705
+ YAML anchors, aliases, and extensions
1706
+
1707
+ </span>
1708
+ </a>
1709
+
1710
+ </li>
1711
+
1712
+ </ul>
1713
+ </nav>
1714
+
1687
1715
  </li>
1688
1716
 
1689
1717
  <li class="md-nav__item">
@@ -4102,6 +4130,34 @@
4102
4130
  </span>
4103
4131
  </a>
4104
4132
 
4133
+ <nav class="md-nav" aria-label="Some YAML Learnin’">
4134
+ <ul class="md-nav__list">
4135
+
4136
+ <li class="md-nav__item">
4137
+ <a href="#yaml-highlights-overview" class="md-nav__link">
4138
+ <span class="md-ellipsis">
4139
+
4140
+ YAML highlights &amp; overview
4141
+
4142
+ </span>
4143
+ </a>
4144
+
4145
+ </li>
4146
+
4147
+ <li class="md-nav__item">
4148
+ <a href="#yaml-anchors-aliases-and-extensions" class="md-nav__link">
4149
+ <span class="md-ellipsis">
4150
+
4151
+ YAML anchors, aliases, and extensions
4152
+
4153
+ </span>
4154
+ </a>
4155
+
4156
+ </li>
4157
+
4158
+ </ul>
4159
+ </nav>
4160
+
4105
4161
  </li>
4106
4162
 
4107
4163
  <li class="md-nav__item">
@@ -4241,10 +4297,20 @@
4241
4297
  example of a complete project configuration.</p>
4242
4298
  </div>
4243
4299
  <h2 id="some-yaml-learnin">Some YAML Learnin’</h2>
4244
- <p>Please consult YAML documentation for the finer points of format
4245
- and to understand details of our YAML-based configuration file.</p>
4246
4300
  <p>We recommend <a href="http://en.wikipedia.org/wiki/Yaml">Wikipedia’s entry on YAML</a>
4247
- for this. A few highlights from that reference page:</p>
4301
+ and <a href="https://www.educative.io/blog/advanced-yaml-syntax-cheatsheet">educative.io’s advanced YAML cheatsheet</a>
4302
+ for a simple overview of YAML and its syntax sufficient for
4303
+ understanding your Ceedling project configuration.</p>
4304
+ <div class="admonition tip">
4305
+ <p class="admonition-title"><code>ceedling dumpconfig</code> to verify your YAML</p>
4306
+ <p>Ceedling provides a means to export the final result of YAML
4307
+ processing for your inspection via <a href="../getting-started/command-line.html#ceedling-dumpconfig-filepath-sections"><code>dumpconfig</code></a>.
4308
+ Dump output includes the resolution of advanced YAML features and
4309
+ Ceedling’s own manipulations. This is a good way to troubleshoot
4310
+ your project configuration.</p>
4311
+ </div>
4312
+ <h3 id="yaml-highlights-overview">YAML highlights &amp; overview</h3>
4313
+ <p>A few points from the preceding references:</p>
4248
4314
  <ul>
4249
4315
  <li>
4250
4316
  <p>YAML streams are encoded using the set of printable Unicode
@@ -4279,12 +4345,16 @@ for this. A few highlights from that reference page:</p>
4279
4345
  punctuation can generally be represented without needing
4280
4346
  to be enclosed in quotes.</p>
4281
4347
  </li>
4282
- <li>
4283
- <p>Repeated nodes are initially denoted by an ampersand (<code>&amp;</code>) and
4284
- thereafter referenced with an asterisk (<code>*</code>). These are known as
4285
- anchors and aliases in YAML speak.</p>
4286
- </li>
4287
4348
  </ul>
4349
+ <h3 id="yaml-anchors-aliases-and-extensions">YAML anchors, aliases, and extensions</h3>
4350
+ <p>YAML provides several “shortcuts” for referencing and modifying blocks
4351
+ of YAML so you can avoid duplication. This is advanced YAML and
4352
+ well beyond the scope of Ceedling documentation. However, know that
4353
+ Ceedling’s YAML parser is entirely capable of handling these advanced
4354
+ aspects of YAML.</p>
4355
+ <p>See this <a href="https://www.educative.io/blog/advanced-yaml-syntax-cheatsheet#anchors">cheatsheet entry</a>
4356
+ and <a href="https://blog.daemonl.com/2016/02/yaml.html">this guide</a>
4357
+ for much more on these powerful but finicky features.</p>
4288
4358
  <h2 id="notes-on-project-file-structure">Notes on Project File Structure</h2>
4289
4359
  <ul>
4290
4360
  <li>
@@ -2344,10 +2344,10 @@
2344
2344
  <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
2345
2345
 
2346
2346
  <li class="md-nav__item">
2347
- <a href="#exmaple-cmock-yaml" class="md-nav__link">
2347
+ <a href="#example-cmock-yaml" class="md-nav__link">
2348
2348
  <span class="md-ellipsis">
2349
2349
 
2350
- Exmaple :cmock YAML
2350
+ Example :cmock YAML
2351
2351
 
2352
2352
  </span>
2353
2353
  </a>
@@ -4046,10 +4046,10 @@
4046
4046
  <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
4047
4047
 
4048
4048
  <li class="md-nav__item">
4049
- <a href="#exmaple-cmock-yaml" class="md-nav__link">
4049
+ <a href="#example-cmock-yaml" class="md-nav__link">
4050
4050
  <span class="md-ellipsis">
4051
4051
 
4052
- Exmaple :cmock YAML
4052
+ Example :cmock YAML
4053
4053
 
4054
4054
  </span>
4055
4055
  </a>
@@ -4155,7 +4155,7 @@
4155
4155
 
4156
4156
  <h1 id="cexception"><code>:cexception</code></h1>
4157
4157
  <p><strong>Configure CException’s features</strong></p>
4158
- <h2 id="exmaple-cmock-yaml">Exmaple <code>:cmock</code> YAML</h2>
4158
+ <h2 id="example-cmock-yaml">Example <code>:cmock</code> YAML</h2>
4159
4159
  <div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="nt">:cexception</span><span class="p">:</span>
4160
4160
  <a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="w"> </span><span class="nt">:defines</span><span class="p">:</span>
4161
4161
  <a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span><span class="c1"># Enables CException `#include`ing CExceptionConfig.h</span>
@@ -2288,10 +2288,10 @@
2288
2288
  <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
2289
2289
 
2290
2290
  <li class="md-nav__item">
2291
- <a href="#exmaple-cmock-yaml" class="md-nav__link">
2291
+ <a href="#example-cmock-yaml" class="md-nav__link">
2292
2292
  <span class="md-ellipsis">
2293
2293
 
2294
- Exmaple :cmock YAML
2294
+ Example :cmock YAML
2295
2295
 
2296
2296
  </span>
2297
2297
  </a>
@@ -4112,10 +4112,10 @@
4112
4112
  <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
4113
4113
 
4114
4114
  <li class="md-nav__item">
4115
- <a href="#exmaple-cmock-yaml" class="md-nav__link">
4115
+ <a href="#example-cmock-yaml" class="md-nav__link">
4116
4116
  <span class="md-ellipsis">
4117
4117
 
4118
- Exmaple :cmock YAML
4118
+ Example :cmock YAML
4119
4119
 
4120
4120
  </span>
4121
4121
  </a>
@@ -4290,7 +4290,7 @@
4290
4290
  <p>Ceedling sets values for a subset of CMock settings. All CMock options are
4291
4291
  available to be set, but only those options set by Ceedling in an automated
4292
4292
  fashion are documented below. See <a href="https://github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md">CMock documentation</a>.</p>
4293
- <h2 id="exmaple-cmock-yaml">Exmaple <code>:cmock</code> YAML</h2>
4293
+ <h2 id="example-cmock-yaml">Example <code>:cmock</code> YAML</h2>
4294
4294
  <div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="nt">:cmock</span><span class="p">:</span>
4295
4295
  <a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="w"> </span><span class="nt">:when_no_prototypes</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">:warn</span>
4296
4296
  <a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span><span class="nt">:enforce_strict_ordering</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">TRUE</span>
@@ -2260,10 +2260,10 @@
2260
2260
  <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
2261
2261
 
2262
2262
  <li class="md-nav__item">
2263
- <a href="#exmaple-unity-yaml" class="md-nav__link">
2263
+ <a href="#example-unity-yaml" class="md-nav__link">
2264
2264
  <span class="md-ellipsis">
2265
2265
 
2266
- Exmaple :unity YAML
2266
+ Example :unity YAML
2267
2267
 
2268
2268
  </span>
2269
2269
  </a>
@@ -4057,10 +4057,10 @@
4057
4057
  <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
4058
4058
 
4059
4059
  <li class="md-nav__item">
4060
- <a href="#exmaple-unity-yaml" class="md-nav__link">
4060
+ <a href="#example-unity-yaml" class="md-nav__link">
4061
4061
  <span class="md-ellipsis">
4062
4062
 
4063
- Exmaple :unity YAML
4063
+ Example :unity YAML
4064
4064
 
4065
4065
  </span>
4066
4066
  </a>
@@ -4177,7 +4177,7 @@
4177
4177
 
4178
4178
  <h1 id="unity"><code>:unity</code></h1>
4179
4179
  <p><strong>Configure Unity’s features</strong></p>
4180
- <h2 id="exmaple-unity-yaml">Exmaple <code>:unity</code> YAML</h2>
4180
+ <h2 id="example-unity-yaml">Example <code>:unity</code> YAML</h2>
4181
4181
  <div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="nt">:unity</span><span class="p">:</span>
4182
4182
  <a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="w"> </span><span class="nt">:defines</span><span class="p">:</span>
4183
4183
  <a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">UNITY_INT_WIDTH=16</span><span class="w"> </span><span class="c1"># 16 bit processor without support for 32 bit instructions</span>