ceedling 1.2.1 → 1.2.2

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 (276) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/README.md +11 -9
  4. data/Rakefile +2 -1
  5. data/assets/features/ceedling +1 -1
  6. data/assets/fixtures/test_source_file_duplicate_basenames/test_calc_typo.c +14 -0
  7. data/assets/fixtures/tests_with_duplicate_test_names/test_foo.c +21 -0
  8. data/bin/actionator.rb +6 -12
  9. data/bin/app_cfg.rb +14 -1
  10. data/bin/ceedling +13 -3
  11. data/bin/cli.rb +25 -6
  12. data/bin/cli_handler.rb +113 -73
  13. data/bin/cli_helper.rb +51 -78
  14. data/bin/installation_selector.rb +81 -0
  15. data/bin/launchinator.rb +69 -0
  16. data/bin/mixin_resolvinator.rb +26 -36
  17. data/bin/objects.yml +13 -0
  18. data/bin/path_validator.rb +25 -29
  19. data/bin/versionator.rb +47 -48
  20. data/ceedling.gemspec +19 -15
  21. data/docs/BreakingChanges.md +70 -1
  22. data/docs/Changelog.md +74 -3
  23. data/docs/KnownIssues.md +90 -4
  24. data/docs/ReleaseNotes.md +13 -0
  25. data/lib/ceedling/build_log_writer.rb +33 -0
  26. data/lib/ceedling/config/build_root_policy.rb +70 -0
  27. data/lib/ceedling/config/configurator.rb +14 -16
  28. data/lib/ceedling/config/configurator_builder.rb +58 -28
  29. data/lib/ceedling/config/configurator_paths_validator.rb +179 -0
  30. data/lib/ceedling/config/configurator_plugins.rb +1 -1
  31. data/lib/ceedling/config/configurator_setup.rb +15 -28
  32. data/lib/ceedling/config/configurator_validator.rb +1 -110
  33. data/lib/ceedling/defaults.rb +4 -4
  34. data/lib/ceedling/dependencies/README.md +18 -16
  35. data/lib/ceedling/dependencies/dependency_cache_store.rb +20 -3
  36. data/lib/ceedling/dependencies/dependency_hasher.rb +35 -1
  37. data/lib/ceedling/filename_extension.rb +7 -1
  38. data/lib/ceedling/filesystem/FILE_TREE.md +253 -0
  39. data/lib/ceedling/filesystem/PATH_HANDLING.md +304 -0
  40. data/lib/ceedling/filesystem/README.md +41 -0
  41. data/lib/ceedling/filesystem/build_subpath.rb +102 -0
  42. data/lib/ceedling/filesystem/destination_collision.rb +25 -0
  43. data/lib/ceedling/{file_finder.rb → filesystem/file_finder.rb} +159 -104
  44. data/lib/ceedling/{file_finder_helper.rb → filesystem/file_finder_helper.rb} +36 -31
  45. data/lib/ceedling/filesystem/file_path_collection_utils.rb +183 -0
  46. data/lib/ceedling/{file_path_utils.rb → filesystem/file_path_utils.rb} +108 -97
  47. data/lib/ceedling/{file_system_wrapper.rb → filesystem/file_system_wrapper.rb} +6 -5
  48. data/lib/ceedling/filesystem/file_tree.rb +241 -0
  49. data/lib/ceedling/{file_wrapper.rb → filesystem/file_wrapper.rb} +45 -38
  50. data/lib/ceedling/filesystem/path_exclusions.rb +30 -0
  51. data/lib/ceedling/{path_matcher.rb → filesystem/path_matcher.rb} +27 -23
  52. data/lib/ceedling/{path_mirror.rb → filesystem/path_mirror.rb} +19 -18
  53. data/lib/ceedling/filesystem/path_spellings.rb +55 -0
  54. data/lib/ceedling/filesystem/project_paths.rb +184 -0
  55. data/lib/ceedling/filesystem/working_dir.rb +40 -0
  56. data/lib/ceedling/generators/generator.rb +5 -6
  57. data/lib/ceedling/generators/generator_partials.rb +1 -1
  58. data/lib/ceedling/generators/generator_test_results_backtrace.rb +7 -7
  59. data/lib/ceedling/handoff.rb +121 -0
  60. data/lib/ceedling/includes/include_factory.rb +3 -3
  61. data/lib/ceedling/includes/include_pathinator.rb +67 -10
  62. data/lib/ceedling/includes/includes.rb +1 -1
  63. data/lib/ceedling/installation.rb +92 -0
  64. data/lib/ceedling/library_resolver.rb +99 -0
  65. data/lib/ceedling/objects.yml +23 -2
  66. data/lib/ceedling/partials/partializer.rb +4 -4
  67. data/lib/ceedling/partials/partializer_config.rb +3 -1
  68. data/lib/ceedling/plugins/report_log_writer_plugin.rb +3 -5
  69. data/lib/ceedling/preprocess/preprocessinator_file_assembler.rb +1 -1
  70. data/lib/ceedling/preprocess/preprocessinator_includes_handler.rb +1 -1
  71. data/lib/ceedling/preprocess/preprocessinator_line_marker_includes_extractor.rb +1 -1
  72. data/lib/ceedling/rake_app/rakefile_component_resolver.rb +10 -17
  73. data/lib/ceedling/rakefile.rb +15 -1
  74. data/lib/ceedling/rakefiles/base/tasks_base.rake +1 -1
  75. data/lib/ceedling/rakefiles/release/rules_release.rake +4 -7
  76. data/lib/ceedling/release_invoker/release_build_executor.rb +33 -44
  77. data/lib/ceedling/release_invoker/release_build_planner.rb +14 -6
  78. data/lib/ceedling/reportinator.rb +3 -4
  79. data/lib/ceedling/setupinator.rb +94 -90
  80. data/lib/ceedling/test_context_extractor.rb +5 -1
  81. data/lib/ceedling/test_invoker/PIPELINE.md +9 -7
  82. data/lib/ceedling/test_invoker/mock_locator.rb +60 -2
  83. data/lib/ceedling/test_invoker/partials_manager.rb +12 -0
  84. data/lib/ceedling/test_invoker/standin_headerinator.rb +22 -13
  85. data/lib/ceedling/test_invoker/test_build_executor.rb +24 -40
  86. data/lib/ceedling/test_invoker/test_build_planner.rb +37 -18
  87. data/lib/ceedling/test_invoker/test_build_setup.rb +17 -12
  88. data/lib/ceedling/test_invoker/test_build_validations.rb +16 -31
  89. data/lib/ceedling/test_invoker/test_file_preprocessinator.rb +1 -1
  90. data/lib/ceedling/test_invoker/test_invoker.rb +6 -1
  91. data/lib/ceedling/test_invoker/test_pipeline_helpers.rb +1 -1
  92. data/lib/ceedling/test_invoker/test_source_file_directive_resolver.rb +40 -20
  93. data/lib/ceedling/tool_executor.rb +9 -3
  94. data/lib/ceedling/yaml_wrapper.rb +3 -2
  95. data/lib/version.rb +1 -1
  96. data/plugins/bullseye/config/defaults_bullseye.rb +1 -1
  97. data/plugins/bullseye/lib/bullseye.rb +9 -7
  98. data/plugins/cppcheck/config/defaults_cppcheck.rb +1 -1
  99. data/plugins/dependencies/config/defaults.yml +6 -6
  100. data/plugins/dependencies/dependencies.rake +4 -1
  101. data/plugins/dependencies/lib/dependencies.rb +18 -8
  102. data/plugins/dependencies/lib/dependency_spec.rb +17 -5
  103. data/plugins/fff/lib/fff.rb +45 -39
  104. data/plugins/fff/spec/fff_cmock_wrapper_spec.rb +128 -0
  105. data/plugins/gcov/config/defaults_gcov.rb +1 -1
  106. data/plugins/gcov/lib/console_reportinator.rb +4 -18
  107. data/plugins/gcov/lib/gcov.rb +9 -2
  108. data/plugins/gcov/lib/gcovr_reportinator.rb +8 -40
  109. data/plugins/gcov/lib/gcovr_thresholds.rb +74 -0
  110. data/plugins/gcov/lib/reportgenerator_reportinator.rb +1 -1
  111. data/plugins/module_generator/lib/module_generator.rb +3 -3
  112. data/plugins/report_tests_teamcity_stdout/lib/report_tests_teamcity_stdout.rb +53 -80
  113. data/plugins/sanitize/lib/sanitize_records.rb +3 -6
  114. data/plugins/valgrind/lib/valgrind.rb +14 -7
  115. data/site-local/configuration/reference/cmock.html +2 -1
  116. data/site-local/configuration/reference/files.html +9 -3
  117. data/site-local/configuration/reference/libraries.html +21 -3
  118. data/site-local/configuration/reference/paths.html +29 -6
  119. data/site-local/configuration/reference/project.html +17 -7
  120. data/site-local/configuration/reference/release-build.html +2 -0
  121. data/site-local/configuration/reference/tools.html +9 -0
  122. data/site-local/configuration/which-ceedling.html +377 -17
  123. data/site-local/development/releases.html +5 -1
  124. data/site-local/development/workflow.html +48 -43
  125. data/site-local/getting-started/builds.html +2 -0
  126. data/site-local/getting-started/command-line.html +56 -4
  127. data/site-local/plugins/dependencies.html +6 -3
  128. data/site-local/plugins/fff.html +42 -11
  129. data/site-local/plugins/gcov/gcovr.html +15 -12
  130. data/site-local/plugins/gcov/setup.html +2 -1
  131. data/site-local/plugins/report-tests-raw-output-log.html +3 -1
  132. data/site-local/plugins/report-tests-teamcity-stdout.html +6 -3
  133. data/site-local/plugins/sanitize.html +7 -6
  134. data/site-local/plugins/valgrind.html +8 -7
  135. data/site-local/project/upgrade.html +3 -0
  136. data/site-local/reference/command-line.html +9 -2
  137. data/site-local/sitemap.xml.gz +0 -0
  138. data/site-local/snapshot/plugins/bullseye/config/defaults_bullseye.rb +1 -1
  139. data/site-local/snapshot/plugins/gcov/config/defaults_gcov.rb +1 -1
  140. data/site-local/testing-guide/conventions.html +53 -0
  141. data/spec/integration/actionator_file_operations_spec.rb +91 -1
  142. data/spec/integration/compile_commands_json_db_spec.rb +1 -1
  143. data/spec/integration/configurator_validation_spec.rb +1 -1
  144. data/spec/integration/dependencies/dependency_tracker_cache_hardening_spec.rb +26 -0
  145. data/spec/integration/dependencies/dependency_tracker_core_spec.rb +22 -0
  146. data/spec/integration/launcher_handoff_spec.rb +297 -0
  147. data/spec/integration/mixin_loading_spec.rb +1 -1
  148. data/spec/integration/module_generator_spec.rb +3 -3
  149. data/spec/integration/nested_test_artifacts_spec.rb +99 -0
  150. data/spec/integration/partials_module_resolution_spec.rb +7 -4
  151. data/spec/integration/path_intake_scenarios_spec.rb +101 -0
  152. data/spec/integration/path_scenarios_spec.rb +322 -0
  153. data/spec/integration/quote_include_isolator_spec.rb +1 -1
  154. data/spec/integration/report_build_warnings_log_spec.rb +1 -1
  155. data/spec/integration/report_tests_log_factory_spec.rb +1 -1
  156. data/spec/integration/test_executable_manifest_spec.rb +1 -1
  157. data/spec/integration/test_includes_extractinator_spec.rb +2 -1
  158. data/spec/integration/test_naming_spec.rb +198 -0
  159. data/spec/integration/test_source_file_checks_spec.rb +166 -0
  160. data/spec/integration/tool_validator_spec.rb +1 -1
  161. data/spec/integration/upgrade_docs_spec.rb +84 -0
  162. data/spec/integration/vendor_copy_transient_io_retry_spec.rb +74 -5
  163. data/spec/support/file_tree_helper.rb +88 -0
  164. data/spec/support/integration/configurator_integration_helper.rb +6 -6
  165. data/spec/support/integration/dependency_tracker_integration_helper.rb +1 -1
  166. data/spec/support/integration/partials_generation_integration_helper.rb +2 -2
  167. data/spec/support/integration/path_scenario_harness.rb +173 -0
  168. data/spec/support/integration/spec_integration_helper.rb +1 -1
  169. data/spec/support/integration/test_includes_harness.rb +13 -2
  170. data/spec/support/spec_helper.rb +2 -0
  171. data/spec/support/system/spec_system_helper.rb +7 -6
  172. data/spec/support/system/system_context.rb +21 -3
  173. data/spec/support/working_dir_tracking.rb +49 -0
  174. data/spec/system/build_output_mirroring_spec.rb +2 -2
  175. data/spec/system/conditional_mock_includes_spec.rb +1 -1
  176. data/spec/system/delta_builds_spec.rb +137 -11
  177. data/spec/system/gcov_deployment_spec.rb +1 -0
  178. data/spec/system/header_include_path_validation_spec.rb +4 -4
  179. data/spec/system/launcher_handoff_spec.rb +82 -0
  180. data/spec/system/mock_include_folders_spec.rb +91 -0
  181. data/spec/system/parent_directory_reference_spec.rb +5 -3
  182. data/spec/system/path_handling_probes_spec.rb +192 -0
  183. data/spec/system/preprocessing_parameterized_tests_spec.rb +1 -1
  184. data/spec/system/rake_release_task_path_disambiguation_spec.rb +6 -6
  185. data/spec/system/rake_test_task_path_disambiguation_spec.rb +85 -0
  186. data/spec/system/special_path_characters_spec.rb +86 -0
  187. data/spec/system/support/common_test_cases.rb +23 -21
  188. data/spec/system/support/gcov_common_test_cases.rb +37 -9
  189. data/spec/system/support/sanitize_common_test_cases.rb +2 -2
  190. data/spec/system/support/valgrind_common_test_cases.rb +12 -12
  191. data/spec/system/test_pipeline_stop_points_spec.rb +4 -4
  192. data/spec/system/test_runner_system_include_spec.rb +1 -1
  193. data/spec/system/test_source_file_directive_spec.rb +34 -5
  194. data/spec/units/bin/actionator_spec.rb +14 -17
  195. data/spec/units/bin/app_cfg_spec.rb +37 -0
  196. data/spec/units/bin/cli_handler_spec.rb +282 -6
  197. data/spec/units/bin/cli_helper_spec.rb +116 -45
  198. data/spec/units/bin/installation_selector_spec.rb +114 -0
  199. data/spec/units/bin/launchinator_spec.rb +171 -0
  200. data/spec/units/bin/path_validator_spec.rb +10 -0
  201. data/spec/units/bin/versionator_spec.rb +38 -6
  202. data/spec/units/build_log_writer_spec.rb +57 -0
  203. data/spec/units/config/build_root_policy_spec.rb +61 -0
  204. data/spec/units/config/configurator_builder_spec.rb +37 -6
  205. data/spec/units/config/configurator_paths_validator_spec.rb +292 -0
  206. data/spec/units/config/configurator_setup_spec.rb +9 -65
  207. data/spec/units/config/configurator_spec.rb +27 -5
  208. data/spec/units/config/configurator_validator_spec.rb +1 -172
  209. data/spec/units/default_tools_quoting_spec.rb +83 -0
  210. data/spec/units/dependencies/dependency_cache_store_spec.rb +33 -2
  211. data/spec/units/dependencies/dependency_debug_tree_spec.rb +1 -1
  212. data/spec/units/dependencies/dependency_hasher_spec.rb +42 -1
  213. data/spec/units/dependencies/dependency_path_normalizer_spec.rb +1 -1
  214. data/spec/units/dependencies/dependency_tracker_spec.rb +3 -1
  215. data/spec/units/filename_extension_spec.rb +11 -0
  216. data/spec/units/filesystem/build_subpath_spec.rb +122 -0
  217. data/spec/units/filesystem/destination_collision_spec.rb +30 -0
  218. data/spec/units/{file_finder_helper_spec.rb → filesystem/file_finder_helper_spec.rb} +46 -1
  219. data/spec/units/{file_finder_spec.rb → filesystem/file_finder_spec.rb} +188 -22
  220. data/spec/units/{file_path_collection_utils_spec.rb → filesystem/file_path_collection_utils_spec.rb} +181 -1
  221. data/spec/units/{file_path_utils_spec.rb → filesystem/file_path_utils_spec.rb} +204 -87
  222. data/spec/units/filesystem/file_system_wrapper_spec.rb +33 -0
  223. data/spec/units/filesystem/file_tree_spec.rb +511 -0
  224. data/spec/units/filesystem/file_tree_windows_spec.rb +78 -0
  225. data/spec/units/{file_wrapper_spec.rb → filesystem/file_wrapper_spec.rb} +95 -12
  226. data/spec/units/{path_matcher_spec.rb → filesystem/path_matcher_spec.rb} +12 -1
  227. data/spec/units/{path_mirror_spec.rb → filesystem/path_mirror_spec.rb} +32 -4
  228. data/spec/units/filesystem/project_paths_spec.rb +147 -0
  229. data/spec/units/filesystem/working_dir_spec.rb +71 -0
  230. data/spec/units/generators/generator_spec.rb +12 -6
  231. data/spec/units/generators/generator_test_results_backtrace_spec.rb +50 -46
  232. data/spec/units/generators/generator_test_results_spec.rb +1 -1
  233. data/spec/units/handoff_spec.rb +205 -0
  234. data/spec/units/includes/include_factory_spec.rb +3 -3
  235. data/spec/units/includes/include_pathinator_spec.rb +46 -0
  236. data/spec/units/installation_spec.rb +228 -0
  237. data/spec/units/library_resolver_spec.rb +157 -0
  238. data/spec/units/mock_locator_spec.rb +76 -1
  239. data/spec/units/partials/partializer_config_spec.rb +7 -0
  240. data/spec/units/partials/partializer_spec.rb +1 -0
  241. data/spec/units/partials_manager_spec.rb +17 -0
  242. data/spec/units/plugins/bullseye_spec.rb +15 -2
  243. data/spec/units/plugins/console_reportinator_spec.rb +18 -20
  244. data/spec/units/plugins/dependencies_spec.rb +33 -7
  245. data/spec/units/plugins/dependency_spec_spec.rb +17 -0
  246. data/spec/units/plugins/gcov_spec.rb +28 -3
  247. data/spec/units/plugins/gcovr_reportinator_spec.rb +19 -47
  248. data/spec/units/plugins/gcovr_thresholds_spec.rb +95 -0
  249. data/spec/units/plugins/module_generator_spec.rb +5 -5
  250. data/spec/units/plugins/report_build_warnings_log_spec.rb +3 -3
  251. data/spec/units/plugins/report_log_writer_plugin_spec.rb +10 -1
  252. data/spec/units/plugins/report_tests_raw_output_log_spec.rb +13 -2
  253. data/spec/units/plugins/report_tests_teamcity_stdout_spec.rb +77 -3
  254. data/spec/units/plugins/reportgenerator_reportinator_spec.rb +1 -1
  255. data/spec/units/plugins/sanitize_records_spec.rb +3 -1
  256. data/spec/units/plugins/sanitize_spec.rb +1 -1
  257. data/spec/units/plugins/valgrind_spec.rb +23 -8
  258. data/spec/units/preprocess/preprocessinator_comment_stripper_spec.rb +1 -1
  259. data/spec/units/rake_app/rakefile_component_resolver_spec.rb +21 -0
  260. data/spec/units/release_build_executor_spec.rb +53 -14
  261. data/spec/units/release_build_planner_spec.rb +15 -3
  262. data/spec/units/reportinator_spec.rb +5 -5
  263. data/spec/units/standin_headerinator_spec.rb +31 -1
  264. data/spec/units/test_build_executor_spec.rb +65 -3
  265. data/spec/units/test_build_planner_spec.rb +104 -2
  266. data/spec/units/test_build_setup_spec.rb +96 -12
  267. data/spec/units/test_context_extractor_spec.rb +25 -0
  268. data/spec/units/test_file_preprocessinator_spec.rb +17 -1
  269. data/spec/units/test_includes_extractinator_spec.rb +5 -1
  270. data/spec/units/test_invoker/objects_wiring_spec.rb +6 -3
  271. data/spec/units/test_invoker_spec.rb +14 -1
  272. data/spec/units/test_source_file_directive_resolver_spec.rb +38 -7
  273. data/spec/units/tool_executor_spec.rb +26 -0
  274. data/spec/units/yaml_wrapper_spec.rb +11 -0
  275. metadata +109 -25
  276. data/lib/ceedling/file_path_collection_utils.rb +0 -164
@@ -26,9 +26,9 @@
26
26
  :name: 'Dependencies archiver'
27
27
  :optional: true # Checked when :build_lib archives, not by every Ceedling command
28
28
  :arguments:
29
- - rcs
30
- - ${2}
31
- - ${1}
29
+ - rcs
30
+ - '"${2}"' # Library
31
+ - '"${1}"' # Objects
32
32
 
33
33
  :deps_zip:
34
34
  :executable: unzip
@@ -36,7 +36,7 @@
36
36
  :optional: true
37
37
  :arguments:
38
38
  - -o
39
- - ${1} # Filepath
39
+ - '"${1}"' # Filepath
40
40
 
41
41
  :deps_targzip:
42
42
  :executable: tar
@@ -57,7 +57,7 @@
57
57
  - clone
58
58
  - ${1} # Optional branch with `-b` flag
59
59
  - ${2} # Optional depth with `--depth` flag
60
- - ${3} # Repository source
60
+ - '"${3}"' # Repository source
61
61
  - .
62
62
 
63
63
  :deps_git_checkout:
@@ -75,7 +75,7 @@
75
75
  :arguments:
76
76
  - checkout
77
77
  - ${1} # Optional branch with `--revision` flag
78
- - ${2} # Repository source
78
+ - '"${2}"' # Repository source
79
79
  - .
80
80
 
81
81
  ...
@@ -89,12 +89,15 @@
89
89
  task :prepare => all_deps
90
90
  end
91
91
 
92
- # Dependency libraries join the library names and paths test and release builds link with
92
+ # Dependency libraries join the library names and paths test and release builds link with,
93
+ # and their files join those the links depend on
93
94
  link_libraries = @ceedling[DEPENDENCIES_SYM].link_libraries
94
95
  PATHS_LIBRARIES ||= []
95
96
  PATHS_LIBRARIES.concat( link_libraries[:paths] ).uniq!
96
97
  LIBRARIES_SYSTEM ||= []
97
98
  LIBRARIES_SYSTEM.concat( link_libraries[:names] ).uniq!
99
+ LIBRARIES_LINK_FILES ||= []
100
+ LIBRARIES_LINK_FILES.concat( link_libraries[:files] ).uniq!
98
101
 
99
102
  # Add any artifact:include or :source folders to our release & test includes paths so linking and mocking work.
100
103
  @ceedling[DEPENDENCIES_SYM].add_headers_and_sources()
@@ -8,6 +8,8 @@
8
8
  require 'ceedling/plugins/plugin'
9
9
  require 'ceedling/constants'
10
10
  require 'ceedling/exceptions'
11
+ require 'ceedling/filesystem/project_paths'
12
+ require 'ceedling/filesystem/working_dir'
11
13
  require 'dependency_spec'
12
14
  require 'dependency_commands'
13
15
  require 'dependency_environment'
@@ -121,13 +123,15 @@ class Dependencies < Plugin
121
123
  end
122
124
  end
123
125
 
124
- # Library names and their directories, for test and release linking
126
+ # Library names and their directories, for test and release linking, and the library files
127
+ # themselves, which those links depend on
125
128
  def link_libraries
126
129
  libraries = @specs.flat_map { |spec| spec.static_libraries + spec.dynamic_libraries }
127
130
 
128
131
  return {
129
132
  names: libraries.map { |library| File.basename( library, '.*' ).delete_prefix( 'lib' ) }.reject( &:empty? ).uniq,
130
- paths: libraries.map { |library| File.dirname( library ) }.reject( &:empty? ).uniq
133
+ paths: libraries.map { |library| File.dirname( library ) }.reject( &:empty? ).uniq,
134
+ files: libraries.uniq
131
135
  }
132
136
  end
133
137
 
@@ -173,7 +177,7 @@ class Dependencies < Plugin
173
177
 
174
178
  # The one place this plugin changes directory, around a dependency's own commands
175
179
  def in_directory(path, &block)
176
- Dir.chdir( path, &block )
180
+ WorkingDir.chdir( path, &block )
177
181
  end
178
182
 
179
183
  # A fetch path that already holds files is taken as fetched, so fetching again, or
@@ -273,7 +277,7 @@ class Dependencies < Plugin
273
277
  end
274
278
 
275
279
  def compile(spec, source, search_paths)
276
- object = object_for( spec, source )
280
+ object = prepared_object( spec, source )
277
281
  @ceedling[:generator].generate_object_file_c(
278
282
  tool: TOOLS_DEPS_COMPILER,
279
283
  module_name: File.basename( source ).ext,
@@ -283,13 +287,13 @@ class Dependencies < Plugin
283
287
  search_paths: search_paths,
284
288
  flags: spec.flags,
285
289
  defines: spec.defines,
286
- list: @ceedling[:file_path_utils].form_release_build_list_filepath( File.basename( source ).ext )
290
+ list: @ceedling[:file_path_utils].form_release_build_list_filepath( object )
287
291
  )
288
292
  return object
289
293
  end
290
294
 
291
295
  def assemble(spec, source)
292
- object = object_for( spec, source )
296
+ object = prepared_object( spec, source )
293
297
  @ceedling[:generator].generate_object_file_asm(
294
298
  tool: TOOLS_DEPS_ASSEMBLER, module_name: File.basename( source ).ext, context: DEPENDENCIES_SYM,
295
299
  source: source, object: object
@@ -297,8 +301,14 @@ class Dependencies < Plugin
297
301
  return object
298
302
  end
299
303
 
300
- def object_for(spec, source)
301
- return File.join( spec.build_path, File.basename( source ).ext( EXTENSION_OBJECT.primary ) )
304
+ # A source's object sits at the source's own path below the dependency's source path, so
305
+ # same-named sources in different directories keep apart. Its directory is made here.
306
+ def prepared_object(spec, source)
307
+ subdir = ProjectPaths.subdir_below( spec.source_path, File.dirname( source ) ) || ''
308
+ object = File.join( spec.build_path, subdir, File.basename( source ).ext( EXTENSION_OBJECT.primary ) )
309
+
310
+ @ceedling[:file_wrapper].mkdir( File.dirname( object ) )
311
+ return object
302
312
  end
303
313
 
304
314
  def copy_library(built, destination)
@@ -6,14 +6,15 @@
6
6
  # =========================================================================
7
7
 
8
8
  require 'ceedling/exceptions'
9
+ require 'ceedling/filesystem/project_paths'
9
10
 
10
11
  # One configured dependency, validated and normalized.
11
12
  #
12
13
  # Each dependency names its paths under :paths, and each path falls back to another when
13
14
  # left out: :source and :fetch to each other, :build to :source then :fetch, :artifact to
14
15
  # :build. With none configured, every path is `dependencies/<task name>`. Artifacts
15
- # (libraries, executables, includes, sources) live under the artifact path. An optional
16
- # :deploy path receives deployed copies.
16
+ # (libraries, executables, includes, sources) live under the artifact path, unless named by
17
+ # an absolute path. An optional :deploy path receives deployed copies.
17
18
  #
18
19
  # The task name is the configured name's word characters, which `dependencies:*` tasks
19
20
  # use, so it must be non-empty and unique among dependencies.
@@ -47,7 +48,7 @@ class DependencySpec
47
48
  @name = dep[:name] or raise CeedlingException.new( 'Each dependency must have a :name.' )
48
49
  @task_name = task_name_for( @name )
49
50
  @fetch = dep[:fetch] || {}
50
- @paths = dep[:paths] || {}
51
+ @paths = standardized( dep[:paths] || {} )
51
52
  @artifacts = dep[:artifacts] || {}
52
53
 
53
54
  problem = fetch_problem
@@ -106,7 +107,7 @@ class DependencySpec
106
107
  # glob expands through `collector` against the disk, and anything else is a directory.
107
108
  def include_directories(collector)
108
109
  return Array( @artifacts[:includes] ).flat_map do |include|
109
- path = File.join( artifact_path, include )
110
+ path = artifact_at( include )
110
111
  if include.end_with?( '.h' ) then [File.dirname( path )]
111
112
  elsif include.match?( INCLUDE_GLOB ) then collector.call( path )
112
113
  else [path]
@@ -128,7 +129,18 @@ class DependencySpec
128
129
  end
129
130
 
130
131
  def artifacts(kind)
131
- return Array( @artifacts[kind] ).map { |path| File.join( artifact_path, path ) }
132
+ return Array( @artifacts[kind] ).map { |path| artifact_at( path ) }
133
+ end
134
+
135
+ # Configured paths, read as every configured path is read
136
+ def standardized(paths)
137
+ return paths.transform_values { |path| path.is_a?( String ) ? ProjectPaths.standardize( path ) : path }
138
+ end
139
+
140
+ # An artifact below the artifact path, or one named by an absolute path as written
141
+ def artifact_at(path)
142
+ path = ProjectPaths.standardize( path )
143
+ return ProjectPaths.absolute?( path ) ? path : File.join( artifact_path, path )
132
144
  end
133
145
 
134
146
  # What is wrong with the fetch configuration, or nil
@@ -10,6 +10,7 @@
10
10
  # ⏩️ https://github.com/ElectronVector/fake_function_framework
11
11
  # =========================================================================
12
12
 
13
+ require 'fileutils'
13
14
  require 'ceedling/plugins/plugin'
14
15
  require 'fff_mock_generator'
15
16
 
@@ -36,16 +37,17 @@ end # class Fff
36
37
 
37
38
  class FffCMockWrapper
38
39
 
39
- def initialize(options=nil)
40
+ def initialize(options=nil)
40
41
  @cm_config = CMockConfig.new(options)
41
42
  @cm_parser = CMockHeaderParser.new(@cm_config)
42
- @silent = (@cm_config.verbosity < 2)
43
-
44
- # These are the additional files to include in the mock files.
45
- @includes_h_pre_orig_header = (@cm_config.includes || @cm_config.includes_h_pre_orig_header || []).map{|h| h.match?(/</) ? h : "\"#{h}\""}
46
- @includes_h_post_orig_header = (@cm_config.includes_h_post_orig_header || []).map{|h| h.match?(/</) ? h : "\"#{h}\""}
47
- @includes_c_pre_header = (@cm_config.includes_c_pre_header || []).map{|h| h.match?(/</) ? h : "\"#{h}\""}
48
- @includes_c_post_header = (@cm_config.includes_c_post_header || []).map{|h| h.match?(/</) ? h : "\"#{h}\""}
43
+ # CMock's levels: 0 errors only, 1 warnings, 2 normal, 3 verbose
44
+ @verbosity = @cm_config.verbosity
45
+
46
+ # The additional files to include in the fake files, gathered as CMock gathers them
47
+ @includes_h_pre_orig_header = quoted( @cm_config.includes, @cm_config.includes_h_pre_orig_header )
48
+ @includes_h_post_orig_header = quoted( @cm_config.includes_h_post_orig_header )
49
+ @includes_c_pre_header = quoted( @cm_config.includes_c_pre_header )
50
+ @includes_c_post_header = quoted( @cm_config.includes_c_post_header )
49
51
  end
50
52
 
51
53
  # Matches CMock's own entry point, which Ceedling calls with the folder a mock's
@@ -57,37 +59,14 @@ class FffCMockWrapper
57
59
  end
58
60
 
59
61
  def generate_mock(header_file_to_mock, folder=nil)
60
- module_name = File.basename(header_file_to_mock, '.h')
61
- puts( "Creating fake functions for #{module_name}..." ) unless @silent
62
- mock_name = @cm_config.mock_prefix + module_name + @cm_config.mock_suffix
63
- mock_path = folder.nil? ? @cm_config.mock_path : File.join(@cm_config.mock_path, folder)
64
- if @cm_config.subdir
65
- # If a subdirectory has been configured, append it to the mock path.
66
- mock_path = "#{mock_path}/#{@cm_config.subdir}"
67
- end
68
- full_path_for_mock = "#{mock_path}/#{mock_name}"
69
- c_name = c_name_for(mock_name, folder)
70
-
71
- # Parse the header file so we know what to mock.
72
- parsed_header = @cm_parser.parse(module_name, File.read(header_file_to_mock))
73
-
74
- # Create the directory if it doesn't exist.
75
- mkdir_p full_path_for_mock.pathmap("%d")
76
-
77
- # Generate the fake function header file.
78
- puts( "Creating fake functions: #{full_path_for_mock}.h" )
79
-
80
- # Create the mock header.
81
- File.open("#{full_path_for_mock}.h", 'w') do |f|
82
- f.write(FffMockGenerator.create_mock_header(module_name, mock_name, parsed_header,
83
- @includes_h_pre_orig_header, @includes_h_post_orig_header, c_name: c_name))
84
- end
85
-
86
- # Create the mock source file.
87
- File.open("#{full_path_for_mock}.c", 'w') do |f|
88
- f.write(FffMockGenerator.create_mock_source(mock_name, parsed_header,
89
- @includes_c_pre_orig_header, @includes_c_post_orig_header, c_name: c_name))
90
- end
62
+ module_name = File.basename(header_file_to_mock, '.h')
63
+ puts( "Creating fake functions for #{module_name}..." ) unless @verbosity < 2
64
+ mock_name = @cm_config.mock_prefix + module_name + @cm_config.mock_suffix
65
+
66
+ # Parse the header file so we know what to mock.
67
+ parsed_header = @cm_parser.parse(module_name, File.read(header_file_to_mock))
68
+
69
+ write_fake( "#{mock_path_for( folder )}/#{mock_name}", module_name, mock_name, parsed_header, c_name_for( mock_name, folder ) )
91
70
  end
92
71
 
93
72
  # The name for a fake's guard and control functions, as CMock forms it. With
@@ -103,6 +82,33 @@ class FffCMockWrapper
103
82
  return identity.gsub( %r{[-/\\.,\s]}, '_' )
104
83
  end
105
84
 
85
+ private
86
+
87
+ # The directory a fake is written to: the mock root, the folder its #include names,
88
+ # then any configured :subdir
89
+ def mock_path_for(folder)
90
+ mock_path = folder.nil? ? @cm_config.mock_path : File.join( @cm_config.mock_path, folder )
91
+ return @cm_config.subdir ? "#{mock_path}/#{@cm_config.subdir}" : mock_path
92
+ end
93
+
94
+ # Writes the fake's header and source at `path`, which ends in the mock's name
95
+ def write_fake(path, module_name, mock_name, parsed_header, c_name)
96
+ FileUtils.mkdir_p( File.dirname( path ) )
97
+
98
+ puts( "Creating fake functions: #{path}.h and #{path}.c" ) unless @verbosity < 3
99
+ File.write( "#{path}.h", FffMockGenerator.create_mock_header( module_name, mock_name, parsed_header,
100
+ @includes_h_pre_orig_header, @includes_h_post_orig_header, c_name: c_name ) )
101
+
102
+ File.write( "#{path}.c", FffMockGenerator.create_mock_source( mock_name, parsed_header,
103
+ @includes_c_pre_header, @includes_c_post_header, c_name: c_name ) )
104
+ end
105
+
106
+ # Joins include lists, each named once, as #include operands. A name holding `<` is
107
+ # kept as written, and any other is quoted, as CMock does.
108
+ def quoted(*lists)
109
+ return lists.compact.flatten.uniq.map { |name| name.match?( /</ ) ? name : "\"#{name}\"" }
110
+ end
111
+
106
112
  end
107
113
 
108
114
  # Switch out the CMock with FFF Mock Generator
@@ -0,0 +1,128 @@
1
+ # =========================================================================
2
+ # Ceedling - Test-Centered Build System for C
3
+ # ThrowTheSwitch.org
4
+ # Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
5
+ # SPDX-License-Identifier: MIT
6
+ # =========================================================================
7
+
8
+ require 'tmpdir'
9
+ require 'fileutils'
10
+
11
+ repo_root = File.expand_path( '../../..', __dir__ )
12
+ $LOAD_PATH.unshift( File.join( repo_root, 'lib' ), File.join( repo_root, 'vendor/cmock/lib' ) )
13
+
14
+ require 'fff.rb'
15
+
16
+ # GH #1377: A fake gets the extra includes CMock would give a mock of the same header.
17
+ # Each example runs the wrapper Ceedling swaps in for CMock, with Ceedling's own
18
+ # :includes default, and reads back the files it writes.
19
+ describe FffCMockWrapper do
20
+
21
+ around(:each) do |example|
22
+ Dir.mktmpdir do |root|
23
+ @root = root
24
+ @header = File.join( root, 'src', 'bar.h' )
25
+ FileUtils.mkdir_p( File.dirname( @header ) )
26
+ File.write( @header, "void bar_turn_on(void);\n" )
27
+ example.run
28
+ end
29
+ end
30
+
31
+ def fake(options = {})
32
+ mock_path = File.join( @root, 'mocks' )
33
+ config = { :mock_prefix => 'mock_', :mock_path => mock_path, :includes => [], :verbosity => 0 }
34
+ FffCMockWrapper.new( config.merge( options ) ).setup_mocks( @header )
35
+ return {
36
+ :h => File.readlines( File.join( mock_path, 'mock_bar.h' ), chomp: true ),
37
+ :c => File.readlines( File.join( mock_path, 'mock_bar.c' ), chomp: true )
38
+ }
39
+ end
40
+
41
+ def position(lines, line)
42
+ index = lines.index( line )
43
+ expect( index ).not_to be_nil, "expected #{line.inspect} in:\n#{lines.join( "\n" )}"
44
+ return index
45
+ end
46
+
47
+ def extra_includes(lines)
48
+ standard = ['#include <string.h>', '#include "fff.h"', '#include "fff_unity_helper.h"', '#include "bar.h"', '#include "mock_bar.h"']
49
+ return lines.grep( /\A#include / ) - standard
50
+ end
51
+
52
+ it 'adds no includes when no setting names any' do
53
+ files = fake
54
+
55
+ expect( extra_includes( files[:h] ) ).to eq( [] )
56
+ expect( extra_includes( files[:c] ) ).to eq( [] )
57
+ end
58
+
59
+ it 'includes :includes_c_pre_header in the source before the fake header' do
60
+ files = fake( :includes_c_pre_header => ['extra_c.h'] )
61
+
62
+ expect( position( files[:c], '#include "extra_c.h"' ) ).to be < position( files[:c], '#include "mock_bar.h"' )
63
+ end
64
+
65
+ it 'includes :includes_c_post_header in the source after the fake header' do
66
+ files = fake( :includes_c_post_header => ['extra_c.h'] )
67
+
68
+ expect( position( files[:c], '#include "extra_c.h"' ) ).to be > position( files[:c], '#include "mock_bar.h"' )
69
+ end
70
+
71
+ it 'includes :includes_h_pre_orig_header in the header before the original header' do
72
+ files = fake( :includes_h_pre_orig_header => ['extra_h.h'] )
73
+
74
+ expect( position( files[:h], '#include "extra_h.h"' ) ).to be < position( files[:h], '#include "bar.h"' )
75
+ end
76
+
77
+ it 'includes :includes_h_post_orig_header in the header after the original header' do
78
+ files = fake( :includes_h_post_orig_header => ['extra_h.h'] )
79
+
80
+ expect( position( files[:h], '#include "extra_h.h"' ) ).to be > position( files[:h], '#include "bar.h"' )
81
+ end
82
+
83
+ it 'merges :includes with :includes_h_pre_orig_header, :includes first, each once' do
84
+ files = fake( :includes => ['first.h', 'both.h'], :includes_h_pre_orig_header => ['both.h', 'second.h'] )
85
+
86
+ expect( extra_includes( files[:h] ) ).to eq( ['#include "first.h"', '#include "both.h"', '#include "second.h"'] )
87
+ end
88
+
89
+ it 'keeps an angle-bracket include as written and quotes a bare name' do
90
+ files = fake( :includes_c_pre_header => ['<stdint.h>', 'extra_c.h'] )
91
+
92
+ expect( extra_includes( files[:c] ) ).to eq( ['#include <stdint.h>', '#include "extra_c.h"'] )
93
+ end
94
+
95
+ it 'includes a Unity helper in the source after the fake header, by its path from the mock root' do
96
+ helper = File.join( @root, 'support', 'helper.h' )
97
+ FileUtils.mkdir_p( File.dirname( helper ) )
98
+ File.write( helper, "\n" )
99
+
100
+ files = fake( :unity_helper_path => [helper] )
101
+
102
+ expect( position( files[:c], '#include "../support/helper.h"' ) ).to be > position( files[:c], '#include "mock_bar.h"' )
103
+ end
104
+
105
+ # CMock's :verbosity levels: 0 errors only, 1 warnings, 2 normal, 3 verbose
106
+ context 'at CMock\'s verbosity levels' do
107
+
108
+ it 'prints nothing for errors only or warnings' do
109
+ expect { fake( :verbosity => 0 ) }.not_to output.to_stdout
110
+ expect { fake( :verbosity => 1 ) }.not_to output.to_stdout
111
+ end
112
+
113
+ it 'announces each fake at normal verbosity, as CMock announces each mock' do
114
+ expect { fake( :verbosity => 2 ) }.to output( "Creating fake functions for bar...\n" ).to_stdout
115
+ end
116
+
117
+ it 'also names the files written at verbose' do
118
+ path = File.join( @root, 'mocks', 'mock_bar' )
119
+
120
+ expect { fake( :verbosity => 3 ) }.to output(
121
+ "Creating fake functions for bar...\n" \
122
+ "Creating fake functions: #{path}.h and #{path}.c\n"
123
+ ).to_stdout
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -41,7 +41,7 @@ DEFAULT_GCOV_LINKER_TOOL = {
41
41
  }
42
42
 
43
43
  DEFAULT_GCOV_FIXTURE_TOOL = {
44
- :executable => '${1}'.freeze,
44
+ :executable => '"${1}"'.freeze, # Test executable, quoted as a path
45
45
  :name => 'default_gcov_fixture'.freeze,
46
46
  :optional => false.freeze,
47
47
  :arguments => [].freeze
@@ -6,7 +6,7 @@
6
6
  # =========================================================================
7
7
 
8
8
  require 'ceedling/constants'
9
- require 'ceedling/path_mirror'
9
+ require 'ceedling/filesystem/path_mirror'
10
10
  require 'gcov_reportinator'
11
11
 
12
12
  class ConsoleReportinator < GcovReportinator
@@ -173,22 +173,8 @@ class ConsoleReportinator < GcovReportinator
173
173
  def run_gcov_summary(test, source, opts)
174
174
  filename = File.basename(source)
175
175
 
176
- # A module-under-test's object (and its accompanying .gcno/.gcda) mirrors its source's own
177
- # subdirectory below whichever configured root it came from -- the same convention its actual
178
- # compile step already follows -- so the directory gcov is told to search must include that
179
- # mirrored subdirectory too, not just the flat <build>/gcov/out/<test name> root.
180
- # A generated Partial sits below its own test's Partials build root rather than a
181
- # configured source root, so the source roots mirror nothing for it. Its own root is
182
- # what carries the module's subdirectory.
183
- roots =
184
- if File.basename(source).match?(PATTERNS::PARTIAL_IMPL_FILENAME)
185
- [File.join( @configurator.project_test_partials_path, test )]
186
- else
187
- @configurator.paths_source + @configurator.paths_support
188
- end
189
-
190
- subdir = PathMirror.relative_subdir( source, roots )
191
- obj_dir = subdir.empty? ? File.join(GCOV_BUILD_OUTPUT_PATH, test) : File.join(GCOV_BUILD_OUTPUT_PATH, test, subdir)
176
+ # The coverage data sits beside the object, wherever the test's build placed it
177
+ obj_dir = File.dirname( @file_path_utils.form_test_object_filepath( source, name: test, context: GCOV_SYM ) )
192
178
 
193
179
  # Run gcov to extract the coverage summary
194
180
  command = @tool_executor.build_command_line(
@@ -197,7 +183,7 @@ class ConsoleReportinator < GcovReportinator
197
183
  (opts[:gcov_mcdc] ? ['-g'] : []),
198
184
  # Argument replacement
199
185
  filename, # .c source file compiled with coverage
200
- obj_dir # <build>/gcov/out/<test name>[/mirrored subdir] for coverage data files
186
+ obj_dir # <build>/gcov/out/<test name>[/source directory] for coverage data files
201
187
  )
202
188
 
203
189
  # Do not raise an exception if `gcov` terminates with a non-zero exit code, just note it and move on.
@@ -12,6 +12,7 @@ require 'gcov_constants'
12
12
  require 'gcov_types'
13
13
  require 'gcov_reportinator'
14
14
  require 'console_reportinator'
15
+ require 'gcovr_thresholds'
15
16
  require 'gcovr_reportinator'
16
17
  require 'reportgenerator_reportinator'
17
18
 
@@ -141,7 +142,7 @@ class Gcov < Plugin
141
142
 
142
143
  # Full filepaths (not basenames)
143
144
  header = "Untested source files not in the coverage report (:untested_sources is :list):"
144
- @loginator.log_list( untested_sources.sort, header, Verbosity::COMPLAIN, LogLabels::WARNING )
145
+ @loginator.log_list( untested_sources.sort, header, Verbosity::COMPLAIN )
145
146
 
146
147
  when GCOV_UNTESTED_SOURCES_COMPILE
147
148
  validate_mcdc_gcc_version!
@@ -163,7 +164,7 @@ class Gcov < Plugin
163
164
  untested_sources.each do |filepath|
164
165
  filename = File.basename(filepath)
165
166
  object = @file_path_utils.form_test_object_filepath( filepath, context:GCOV_SYM )
166
- dependencies = @file_path_utils.form_test_dependencies_filepath( filepath, context:GCOV_SYM )
167
+ dependencies = @file_path_utils.form_test_dependencies_filepath( object, context:GCOV_SYM )
167
168
  search_paths = @configurator.collection_paths_include
168
169
  # Defines and flags resolve as a tested source's do, against this source's filepath
169
170
  defines = @test_build_setup.context_defines( context:GCOV_SYM, filepath:filepath )
@@ -196,6 +197,9 @@ class Gcov < Plugin
196
197
  next
197
198
  end
198
199
 
200
+ @file_wrapper.mkdir( File.dirname( object ) )
201
+ @file_wrapper.mkdir( File.dirname( dependencies ) )
202
+
199
203
  begin
200
204
  @generator.generate_object_file_c(
201
205
  tool: TOOLS_GCOV_COMPILER,
@@ -382,6 +386,9 @@ class Gcov < Plugin
382
386
 
383
387
  begin
384
388
  reportinator.generate_reports( @configurator.project_config_hash )
389
+ rescue GcovrThresholdError => ex
390
+ # Each violated threshold is its own build failure
391
+ ex.violations.each { |violation| @ceedling[:plugin_manager].register_build_failure( GCOV_SYM, violation ) }
385
392
  rescue => ex
386
393
  # Register the exception as a build failure
387
394
  @ceedling[:plugin_manager].register_build_failure( GCOV_SYM, ex.message )
@@ -7,8 +7,10 @@
7
7
 
8
8
  require 'ceedling/exceptions'
9
9
  require 'ceedling/constants'
10
+ require 'ceedling/filename_extension'
10
11
  require 'gcov_types'
11
12
  require 'gcov_reportinator'
13
+ require 'gcovr_thresholds'
12
14
 
13
15
  class GcovrReportinator < GcovReportinator
14
16
 
@@ -507,9 +509,8 @@ class GcovrReportinator < GcovReportinator
507
509
  data = build_exclusion_data
508
510
  patterns = []
509
511
 
510
- # A source extension can be configured as more than one string, so its escaped forms
511
- # are joined into one alternation rather than assuming there's only ever one to match.
512
- src_extension_alternation = data[:src_extension].to_a.map { |ext| Regexp.escape(ext) }.join('|')
512
+ # A source extension can be configured as more than one string, so any one of them matches
513
+ src_extension_alternation = FilenameExtension.new( data[:src_extension] ).pattern
513
514
  test_prefix = Regexp.escape(data[:test_prefix].to_s)
514
515
  build_root = Regexp.escape(strip_leading_dot_slash(data[:build_root].to_s))
515
516
 
@@ -576,23 +577,6 @@ class GcovrReportinator < GcovReportinator
576
577
  end
577
578
 
578
579
 
579
- # Returns the error message text from gcovr stderr, or nil if none found.
580
- # Matches lines beginning with "error" or "(ERROR)" and extracts the trailing message.
581
- def extract_gcovr_error_message(shell_result)
582
- return nil if shell_result.nil?
583
-
584
- stderr = shell_result[:stderr]
585
- return nil if stderr.nil? || stderr.empty?
586
-
587
- stderr.each_line do |line|
588
- match = line.match( /^\(?error\S*\s+(.+)/i )
589
- return match[1].strip.capitalize if match
590
- end
591
-
592
- nil
593
- end
594
-
595
-
596
580
  # Extract the last contiguous block of lines containing '%' from gcovr stdout.
597
581
  # gcovr --print-summary emits lines like "lines: 69.6% (80 out of 115)" — this
598
582
  # locates that block generically without depending on exact line labels.
@@ -620,33 +604,17 @@ class GcovrReportinator < GcovReportinator
620
604
  end
621
605
 
622
606
 
623
- # Output to console a human-friendly message on certain coverage failure exit codes.
624
- # Prefers the actual gcovr error text from stderr; falls back to descriptive strings.
625
- # gcovr's composite exit code bit-ORs every violated :fail_under_* threshold together --
626
- # every violated bit is collected and reported, not just whichever is checked first.
607
+ # Reports every :fail_under_* threshold a gcovr run violated, raising when boom is set and
608
+ # logging otherwise. Returns whether the exit code holds a problem no threshold explains.
627
609
  def gcovr_exec_exception?(opts, exitcode, boom, shell_result=nil)
628
- violations = []
629
-
630
- {
631
- 2 => [:fail_under_line, 'Line'],
632
- 4 => [:fail_under_branch, 'Branch'],
633
- 8 => [:fail_under_decision, 'Decision'],
634
- 16 => [:fail_under_function, 'Function'],
635
- }.each do |bit, (option, label)|
636
- next unless ((exitcode & bit) == bit) && !opts[option].nil?
637
-
638
- fallback = "#{label} coverage is less than the configured minimum of #{opts[option]}%"
639
- violations << "Gcovr ⏩️ #{extract_gcovr_error_message( shell_result ) || fallback}"
640
- exitcode &= ~bit
641
- end
610
+ violations, exitcode = GcovrThresholds.decode( opts: opts, exitcode: exitcode, stderr: shell_result&.dig( :stderr ) )
642
611
 
643
612
  if boom
644
- raise CeedlingException.new( violations.join("\n") ) unless violations.empty?
613
+ raise GcovrThresholdError.new( violations ) unless violations.empty?
645
614
  else
646
615
  violations.each { |msg| @loginator.log( msg, Verbosity::COMPLAIN ) }
647
616
  end
648
617
 
649
- # A non-zero exit code is a problem
650
618
  return (exitcode != 0)
651
619
  end
652
620