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
@@ -42,7 +42,7 @@ describe Valgrind do
42
42
  let(:plugin_manager) { double('plugin_manager', register_build_failure: nil) }
43
43
  let(:plugin_reportinator) { double('plugin_reportinator') }
44
44
  let(:dependinator) { double('dependinator', register: nil) }
45
- let(:file_wrapper) { double('file_wrapper', exist?: true, read: '', mkdir: nil) }
45
+ let(:file_wrapper) { double('file_wrapper', exist_with_retry?: true, read: '', mkdir: nil) }
46
46
  let(:system_wrapper) { double('system_wrapper', windows?: false) }
47
47
  let(:ceedling) do
48
48
  {
@@ -207,13 +207,28 @@ describe Valgrind do
207
207
  end
208
208
 
209
209
  describe '#pre_test_fixture_register' do
210
- def register(project_config, context: VALGRIND_SYM)
210
+ before(:each) do
211
+ allow(file_wrapper).to receive(:mkdir_for) { |path| path }
212
+ end
213
+
214
+ def register(project_config, context: VALGRIND_SYM, test_name: 'test_thing')
211
215
  valgrind = build_valgrind(project_config)
212
- arg_hash = { context: context, test_name: 'test_thing', target: 'build/out/test_thing.out', tool: { name: 'original' } }
216
+ arg_hash = { context: context, test_name: test_name, target: 'build/out/test_thing.out', tool: { name: 'original' } }
213
217
  valgrind.pre_test_fixture_register(arg_hash)
214
218
  [valgrind, arg_hash]
215
219
  end
216
220
 
221
+ # GH #1364: Valgrind writes its reports but makes no directories of its own
222
+ it "makes the directories a nested test's reports go in" do
223
+ expect(file_wrapper).to receive(:mkdir_for).with('artifacts/valgrind/test/unit/test_thing.log')
224
+ expect(file_wrapper).to receive(:mkdir_for).with('artifacts/valgrind/test/unit/test_thing.xml')
225
+
226
+ _valgrind, arg_hash = register({ valgrind_xml_report: true }, test_name: 'test/unit/test_thing')
227
+
228
+ expect(arg_hash[:tool][:arguments]).to include('--log-file="artifacts/valgrind/test/unit/test_thing.log"')
229
+ expect(arg_hash[:tool][:arguments]).to include('--xml-file="artifacts/valgrind/test/unit/test_thing.xml"')
230
+ end
231
+
217
232
  it 'leaves a non-valgrind context untouched' do
218
233
  original = { name: 'original' }
219
234
  valgrind = build_valgrind({})
@@ -232,10 +247,10 @@ describe Valgrind do
232
247
  expect(arg_hash[:tool][:arguments]).to include('--log-file="artifacts/valgrind/test_thing.log"')
233
248
  end
234
249
 
235
- it 'appends the executable substitution token last' do
250
+ it 'appends the executable substitution token last, quoted as a path' do
236
251
  _valgrind, arg_hash = register({ valgrind_arguments: ['--leak-check=full'] })
237
252
 
238
- expect(arg_hash[:tool][:arguments].last).to eq('${1}')
253
+ expect(arg_hash[:tool][:arguments].last).to eq('"${1}"')
239
254
  end
240
255
 
241
256
  it 'places user arguments after the plugin-managed ones' do
@@ -315,7 +330,7 @@ describe Valgrind do
315
330
  end
316
331
 
317
332
  def explain(report, project_config: {}, context: VALGRIND_SYM, crash_notice: true, exists: true)
318
- wrapper = double('file_wrapper', exist?: exists, read: report)
333
+ wrapper = double('file_wrapper', exist_with_retry?: exists, read: report)
319
334
  valgrind = build_valgrind(project_config, file_wrapper: wrapper)
320
335
  arg_hash = { context: context, test_name: 'test_thing', crash_notice: crash_notice, explanation: nil }
321
336
  valgrind.explain_test_crash( arg_hash )
@@ -384,7 +399,7 @@ describe Valgrind do
384
399
 
385
400
  describe '#post_test_fixture_execute' do
386
401
  def execute(project_config, log_contents, exists: true, result_file: File.join('build', 'valgrind', 'results', 'test_thing.pass'))
387
- wrapper = double('file_wrapper', exist?: exists, read: log_contents)
402
+ wrapper = double('file_wrapper', exist_with_retry?: exists, read: log_contents)
388
403
  valgrind = build_valgrind(project_config, file_wrapper: wrapper)
389
404
  valgrind.post_test_fixture_execute(
390
405
  { context: VALGRIND_SYM, test_name: 'test_thing', result_file: result_file }
@@ -451,7 +466,7 @@ describe Valgrind do
451
466
 
452
467
  # A silent skip reads exactly like a clean result. It is not one.
453
468
  it 'warns and records nothing when no report was written' do
454
- expect(loginator).to receive(:log).with(/wrote no report for test_thing/, anything, anything)
469
+ expect(loginator).to receive(:log).with(/wrote no report for test_thing/, Verbosity::COMPLAIN)
455
470
 
456
471
  valgrind = execute({}, '', exists: false)
457
472
 
@@ -9,7 +9,7 @@ require 'spec_helper'
9
9
  require 'ceedling/preprocess/c_comment_scanner'
10
10
  require 'ceedling/preprocess/preprocessinator_comment_stripper'
11
11
  require 'ceedling/preprocess/preprocessinator_code_finder'
12
- require 'ceedling/file_wrapper'
12
+ require 'ceedling/filesystem/file_wrapper'
13
13
 
14
14
  RSpec.describe PreprocessinatorCommentStripper do
15
15
 
@@ -33,6 +33,18 @@ describe RakefileComponentResolver do
33
33
 
34
34
  expect(result).to eq(['ceedling/plugins'])
35
35
  end
36
+
37
+ # The path handling audit around GH #1370: bin/ reads :load_paths before the
38
+ # configuration pipeline, so it must expand and standardize them as that pipeline does
39
+ it 'expands and standardizes each configured load path, leaving the configuration as written' do
40
+ config = { plugins: { load_paths: ["\#{'my'}\\plugins\\", '~/plugins'] } }
41
+ expand = ->(path) { path.sub( "\#{'my'}", 'my' ) }
42
+
43
+ result = described_class.prepare_plugin_load_paths(config, 'ceedling/plugins', expand: expand)
44
+
45
+ expect(result).to eq(['my/plugins', File.join( Dir.home, 'plugins' ), 'ceedling/plugins'])
46
+ expect(config[:plugins][:load_paths].first).to eq( "\#{'my'}\\plugins\\" )
47
+ end
36
48
  end
37
49
 
38
50
  describe '.gather_rakefiles' do
@@ -125,6 +137,15 @@ describe RakefileComponentResolver do
125
137
  File.join('ceedling/plugins', 'gcov', 'gcov.rake'),
126
138
  ])
127
139
  end
140
+
141
+ it 'finds a plugin below a load path that needs expanding' do
142
+ config = { project: { release_build: false }, plugins: { enabled: ['mine'], load_paths: ["\#{'my'}/plugins"] } }
143
+ allow(File).to receive(:exist?).with(File.join('my/plugins', 'mine', 'mine.rake')).and_return(true)
144
+
145
+ result = described_class.resolve(config, 'lib', 'ceedling/plugins', expand: ->(path) { path.sub( "\#{'my'}", 'my' ) })
146
+
147
+ expect(result.last).to eq( File.join('my/plugins', 'mine', 'mine.rake') )
148
+ end
128
149
  end
129
150
 
130
151
  describe '.plugin_rake_files' do
@@ -23,6 +23,7 @@ describe ReleaseBuildExecutor do
23
23
  @file_wrapper = double( "FileWrapper" )
24
24
  @file_path_utils = double( "FilePathUtils" )
25
25
  @dependinator = double( "Dependinator" )
26
+ @library_resolver = double( "LibraryResolver" )
26
27
 
27
28
  @tools_release_compiler = { name: 'fake compiler' }
28
29
  @tools_release_assembler = { name: 'fake assembler' }
@@ -56,6 +57,8 @@ describe ReleaseBuildExecutor do
56
57
  allow(@dependinator).to receive(:register_gcc_deps_file)
57
58
  allow(@dependinator).to receive(:stale?).and_return( true )
58
59
  allow(@dependinator).to receive(:mark_fresh)
60
+ allow(@library_resolver).to receive(:link_files).and_return( [] )
61
+ allow(@library_resolver).to receive(:search_path_arguments).and_return( [] )
59
62
 
60
63
  @executor = described_class.new(
61
64
  {
@@ -67,7 +70,8 @@ describe ReleaseBuildExecutor do
67
70
  :file_finder => @file_finder,
68
71
  :file_wrapper => @file_wrapper,
69
72
  :file_path_utils => @file_path_utils,
70
- :dependinator => @dependinator
73
+ :dependinator => @dependinator,
74
+ :library_resolver => @library_resolver
71
75
  }
72
76
  )
73
77
 
@@ -278,6 +282,18 @@ describe ReleaseBuildExecutor do
278
282
  @executor.link( @state )
279
283
  end
280
284
 
285
+ it "separates every configured library extension, and a subproject's, from plain objects" do
286
+ stub_const( "EXTENSION_LIBRARIES", FilenameExtension.new( ['.a', '.so'] ) )
287
+ stub_const( "EXTENSION_SUBPROJECTS", '.lib' )
288
+ @state.objects = ['build/release/out/foo.o', 'vendor/libbar.a', 'vendor/libbaz.so', 'sub/x.lib', 'build/release/out/so.o']
289
+
290
+ expect(@generator).to receive(:generate_executable_file) do |_tool, _sym, objects, *_rest|
291
+ expect(objects).to eq( ['build/release/out/foo.o', 'build/release/out/so.o'] )
292
+ end
293
+
294
+ @executor.link( @state )
295
+ end
296
+
281
297
  it "formats a separated library as a linker argument via the configured flag template" do
282
298
  stub_const( "EXTENSION_LIBRARIES", FilenameExtension.new('.a') )
283
299
  stub_const( "LIBRARIES_FLAG", '-l${1}' )
@@ -302,9 +318,8 @@ describe ReleaseBuildExecutor do
302
318
  @executor.link( @state )
303
319
  end
304
320
 
305
- it "formats configured library search paths as linker arguments via the configured flag template" do
306
- stub_const( "PATHS_LIBRARIES", ['vendor/lib'] )
307
- stub_const( "LIBRARIES_PATH_FLAG", '-L${1}' )
321
+ it "links with the library search path arguments the resolver gives" do
322
+ allow(@library_resolver).to receive(:search_path_arguments).and_return( ['-Lvendor/lib'] )
308
323
 
309
324
  expect(@generator).to receive(:generate_executable_file) do |_tool, _sym, _objects, _flags, _target, _map, _lib_args, lib_paths|
310
325
  expect(lib_paths).to eq( ['-Lvendor/lib'] )
@@ -313,14 +328,12 @@ describe ReleaseBuildExecutor do
313
328
  @executor.link( @state )
314
329
  end
315
330
 
316
- # Quoted like every other default path argument, so a path with a space survives the shell
317
- it "quotes each library search path under the default flag template" do
318
- stub_const( "PATHS_LIBRARIES", ['vendor/third party/lib'] )
319
- stub_const( "LIBRARIES_PATH_FLAG", DEFAULT_CEEDLING_PROJECT_CONFIG[:libraries][:path_flag] )
331
+ # A target named by an absolute :output may sit in a directory no build made
332
+ it "makes the target's directory before linking" do
333
+ allow(@configurator).to receive(:project_release_build_target).and_return( '/elsewhere/out/app.elf' )
320
334
 
321
- expect(@generator).to receive(:generate_executable_file) do |_tool, _sym, _objects, _flags, _target, _map, _lib_args, lib_paths|
322
- expect(lib_paths).to eq( ['-L "vendor/third party/lib"'] )
323
- end
335
+ expect(@file_wrapper).to receive(:mkdir).with( '/elsewhere/out' ).ordered
336
+ expect(@generator).to receive(:generate_executable_file).ordered
324
337
 
325
338
  @executor.link( @state )
326
339
  end
@@ -338,14 +351,40 @@ describe ReleaseBuildExecutor do
338
351
  it "registers the resolved link flags and library arguments, not just the object files, as meta" do
339
352
  stub_const( "LIBRARIES_SYSTEM", ['m'] )
340
353
  stub_const( "LIBRARIES_FLAG", '-l${1}' )
341
- stub_const( "PATHS_LIBRARIES", ['vendor/lib'] )
342
- stub_const( "LIBRARIES_PATH_FLAG", '-L${1}' )
354
+ allow(@library_resolver).to receive(:search_path_arguments).and_return( ['-Lvendor/lib'] )
343
355
  @state.link_flags = ['-Wall']
344
356
 
345
357
  expect(@dependinator).to receive(:register).with(
346
358
  'build/release/out/project.out',
347
359
  files: ['build/release/out/foo.o'],
348
- meta: { flags: ['-Wall'], lib_args: ['-lm'], lib_paths: ['-Lvendor/lib'], tools: [@tools_release_linker] }
360
+ meta: { flags: ['-Wall'], lib_args: ['-lm'], lib_paths: ['-Lvendor/lib'], tools: [@tools_release_linker], objects: ['build/release/out/foo.o'], libraries: [] }
361
+ )
362
+
363
+ @executor.link( @state )
364
+ end
365
+
366
+ # A source dropped from the build leaves every remaining object as it was, so only the
367
+ # object list, as meta, relinks an artifact that would otherwise keep the dropped code
368
+ it "registers the objects it links as meta" do
369
+ expect(@dependinator).to receive(:register).with(
370
+ 'build/release/out/project.out', files: ['build/release/out/foo.o'], meta: hash_including( objects: ['build/release/out/foo.o'] )
371
+ )
372
+
373
+ @executor.link( @state )
374
+ end
375
+
376
+ # GH #1368: a library rebuilt under the same name changed nothing a link registered
377
+ it "registers the library files its libraries resolve to, including those named by path, as files and as meta" do
378
+ @state.objects = ['build/release/out/foo.o', 'vendor/prebuilt/libp.a']
379
+ stub_const( 'EXTENSION_LIBRARIES', FilenameExtension.new( ['.a'] ) )
380
+ stub_const( 'LIBRARIES_SYSTEM', ['m'] )
381
+ stub_const( 'LIBRARIES_RELEASE', ['x'] )
382
+
383
+ expect(@library_resolver).to receive(:link_files).with( ['vendor/prebuilt/libp.a', 'm', 'x'] ).and_return( ['vendor/prebuilt/libp.a', 'vendor/lib/libx.a', 'build/deps/libfusion.a'] )
384
+ expect(@dependinator).to receive(:register).with(
385
+ 'build/release/out/project.out',
386
+ files: ['build/release/out/foo.o', 'vendor/prebuilt/libp.a', 'vendor/lib/libx.a', 'build/deps/libfusion.a'],
387
+ meta: hash_including( libraries: ['vendor/prebuilt/libp.a', 'vendor/lib/libx.a', 'build/deps/libfusion.a'] )
349
388
  )
350
389
 
351
390
  @executor.link( @state )
@@ -51,9 +51,21 @@ describe ReleaseBuildPlanner do
51
51
  end
52
52
 
53
53
  it "includes extra link-only objects alongside the compiled sources" do
54
- allow(@configurator).to receive(:collection_release_artifact_extra_link_objects).and_return( ['vendor/cexception/CException.o'] )
55
- expect(@file_path_utils).to receive(:form_release_build_objects_filelist).with( ['src/Foo.c', 'vendor/cexception/CException.o'] ).and_return( [] )
54
+ allow(@configurator).to receive(:collection_release_artifact_extra_link_objects).and_return( ['CException.o'] )
56
55
  @planner.plan( @state, files: nil )
56
+ expect( @state.objects ).to eq( ['build/release/out/Foo.o', 'build/release/out/CException.o'] )
57
+ end
58
+
59
+ # A link-only name is no source of its own, so naming the object a compiled source builds
60
+ # to is no collision between two sources
61
+ it "places link-only objects apart from the compiled sources, adding a shared object once" do
62
+ allow(@configurator).to receive(:collection_release_build_input).and_return( ['build/vendor/c_exception/lib/CException.c'] )
63
+ allow(@configurator).to receive(:collection_release_artifact_extra_link_objects).and_return( ['CException.o'] )
64
+ expect(@file_path_utils).to receive(:form_release_build_objects_filelist).with( ['build/vendor/c_exception/lib/CException.c'] ).and_return( ['build/release/out/CException.o'] )
65
+ expect(@file_path_utils).to receive(:form_release_build_objects_filelist).with( ['CException.o'] ).and_return( ['build/release/out/CException.o'] )
66
+
67
+ @planner.plan( @state, files: nil )
68
+ expect( @state.objects ).to eq( ['build/release/out/CException.o'] )
57
69
  end
58
70
 
59
71
  it "scopes the object list to just the given file when files is provided, ignoring the full source collection" do
@@ -124,7 +136,7 @@ describe ReleaseBuildPlanner do
124
136
 
125
137
  expect( compiler[:arguments] ).to include( '-fPIC' )
126
138
  expect( linker[:executable] ).to eq( 'ar' )
127
- expect( linker[:arguments] ).to eq( ['rcs', '${2}', '${1}'] )
139
+ expect( linker[:arguments] ).to eq( ['rcs', '"${2}"', '"${1}"'] )
128
140
  end
129
141
 
130
142
  it "does nothing when the project has configured its own compiler tool (not the Ceedling default)" do
@@ -225,14 +225,14 @@ describe Reportinator do
225
225
  expect(result).to eq("Compiling bar::baz.c...")
226
226
  end
227
227
 
228
- it 'omits module label when filename matches only the last segment of a mirrored, multi-segment module name' do
229
- result = @rp.generate_module_progress(module_name: 'adc/TestFoo', filename: 'TestFoo.c', operation: 'Compiling')
228
+ it 'omits module label when filename matches only the last segment of a multi-segment module name' do
229
+ result = @rp.generate_module_progress(module_name: 'test/adc/TestFoo', filename: 'TestFoo.c', operation: 'Compiling')
230
230
  expect(result).to eq("Compiling TestFoo.c...")
231
231
  end
232
232
 
233
- it 'prepends the full mirrored module name when filename differs from its last segment' do
234
- result = @rp.generate_module_progress(module_name: 'adc/TestFoo', filename: 'MockBar.c', operation: 'Generating mock for')
235
- expect(result).to eq("Generating mock for adc/TestFoo::MockBar.c...")
233
+ it 'prepends the full module name when filename differs from its last segment' do
234
+ result = @rp.generate_module_progress(module_name: 'test/adc/TestFoo', filename: 'MockBar.c', operation: 'Generating mock for')
235
+ expect(result).to eq("Generating mock for test/adc/TestFoo::MockBar.c...")
236
236
  end
237
237
  end
238
238
 
@@ -10,6 +10,7 @@ require 'ceedling/constants'
10
10
  require 'ceedling/includes/includes'
11
11
  require 'ceedling/test_invoker/test_invoker_types'
12
12
  require 'ceedling/test_invoker/standin_headerinator'
13
+ require 'ceedling/test_invoker/mock_locator'
13
14
 
14
15
  describe StandinHeaderinator do
15
16
  before(:each) do
@@ -33,6 +34,11 @@ describe StandinHeaderinator do
33
34
  end
34
35
  allow(@mock_locator).to receive(:header_collection).and_return( ['src/drivers/foo.h'] )
35
36
  allow(@mock_locator).to receive(:locate).and_return( ['src/drivers/foo.h', 'drivers'] )
37
+ allow(@mock_locator).to receive(:placement) do |mocks_root:, subdir:, **|
38
+ dir = subdir.empty? ? mocks_root : File.join( mocks_root, subdir )
39
+ MockLocator::MockPlacement.new( folder: nil, directory: dir, root: dir, reach: dir )
40
+ end
41
+ allow(@mock_locator).to receive(:search_directories) { |placement| [placement.directory] }
36
42
 
37
43
  @standins = described_class.new(
38
44
  configurator: @configurator, loginator: @loginator, reportinator: @reportinator,
@@ -110,9 +116,33 @@ describe StandinHeaderinator do
110
116
  expect(dirs).to eq( ['build/test/mocks/a_test/drivers'] )
111
117
  end
112
118
 
113
- it "gives no directories when the test has no mock root" do
119
+ # The test's #include reaches the stand-in only from the directory the folder climbs from, so it must exist
120
+ it "writes a climbing folder's stand-in where its mock will be, giving and making the directory it is reached from" do
121
+ climbing = MockLocator::MockPlacement.new( folder: '../x', directory: 'build/test/mocks/a_test/_up_/x', root: 'build/test/mocks/a_test/_up_/_up_', reach: 'build/test/mocks/a_test/_up_/_up_' )
122
+ allow(@mock_locator).to receive(:placement).with( mocks_root: 'build/test/mocks/a_test', subdir: 'drivers', include_path: '../x/MockFoo.h' ).and_return( climbing )
123
+ allow(@mock_locator).to receive(:search_directories).with( climbing ).and_return( [climbing.directory, climbing.root] )
124
+
125
+ expect(@file_wrapper).to receive(:mkdir).with( 'build/test/mocks/a_test/_up_/_up_' )
126
+ expect(@file_wrapper).to receive(:write).with( 'build/test/mocks/a_test/_up_/x/MockFoo.h', anything )
127
+
128
+ expect(@standins.place( @testable, [Include.new( '../x/MockFoo.h' )] ))
129
+ .to eq( ['build/test/mocks/a_test/_up_/x', 'build/test/mocks/a_test/_up_/_up_'] )
130
+ end
131
+
132
+ # The real mock opens with the header as CMock names it, by the folder CMock is given
133
+ it "includes the header by the folder its mock is named by" do
134
+ named = MockLocator::MockPlacement.new( folder: 'drivers', directory: 'build/test/mocks/a_test/drivers', root: 'build/test/mocks/a_test', reach: 'build/test/mocks/a_test' )
135
+ allow(@mock_locator).to receive(:placement).and_return( named )
136
+
137
+ expect(@file_wrapper).to receive(:write).with( 'build/test/mocks/a_test/drivers/MockFoo.h', "#{StandinHeaderinator::MARKER}\n#include \"drivers/foo.h\"\n" )
138
+
139
+ @standins.place( @testable, [Include.new( 'drivers/MockFoo.h' )] )
140
+ end
141
+
142
+ it "places no stand-in and gives no directories when the test has no mock root" do
114
143
  @testable.paths = {}
115
144
 
145
+ expect(@file_wrapper).to_not receive(:write)
116
146
  expect(@standins.place( @testable, [Include.new( 'MockFoo.h' )] )).to eq( [] )
117
147
  end
118
148
 
@@ -9,6 +9,7 @@ require 'spec_helper'
9
9
  require 'ceedling/filename_extension'
10
10
  require 'rake'
11
11
  require 'ceedling/test_invoker/test_build_executor'
12
+ require 'ceedling/test_invoker/mock_locator'
12
13
  require 'ceedling/test_invoker/test_invoker_types'
13
14
  require 'ceedling/partials/partials'
14
15
  require 'ceedling/test_context_extractor'
@@ -35,6 +36,7 @@ describe TestBuildExecutor do
35
36
  @gcc_dependency_parser = double( "GccDependencyParser" )
36
37
  @quote_include_isolator = double( "QuoteIncludeIsolator" )
37
38
  @generator_helper = double( "GeneratorHelper" )
39
+ @library_resolver = double( "LibraryResolver" )
38
40
 
39
41
  @tools_test_compiler = { name: 'fake compiler' }
40
42
  @tools_test_assembler = { name: 'fake assembler' }
@@ -107,6 +109,8 @@ describe TestBuildExecutor do
107
109
  allow(@dependinator).to receive(:stale?).and_return( true )
108
110
  allow(@dependinator).to receive(:mark_fresh)
109
111
  allow(@test_source_file_directive_resolver).to receive(:validate!)
112
+ allow(@library_resolver).to receive(:link_files).and_return( [] )
113
+ allow(@library_resolver).to receive(:search_path_arguments).and_return( [] )
110
114
 
111
115
  @executor = described_class.new(
112
116
  {
@@ -123,7 +127,11 @@ describe TestBuildExecutor do
123
127
  :dependinator => @dependinator,
124
128
  :gcc_dependency_parser => @gcc_dependency_parser,
125
129
  :quote_include_isolator => @quote_include_isolator,
126
- :generator_helper => @generator_helper
130
+ :generator_helper => @generator_helper,
131
+ :library_resolver => @library_resolver,
132
+ :mock_locator => MockLocator.new(
133
+ configurator: @configurator, include_pathinator: double( 'include_pathinator' ), file_finder: @file_finder, file_path_utils: @file_path_utils
134
+ )
127
135
  }
128
136
  )
129
137
 
@@ -1059,12 +1067,42 @@ describe TestBuildExecutor do
1059
1067
  expect(@dependinator).to receive(:register).with(
1060
1068
  'build/a_test.out',
1061
1069
  files: ['build/foo.o'],
1062
- meta: { flags: [], lib_args: [], lib_paths: [], tools: [@tools_test_linker] }
1070
+ meta: { flags: [], lib_args: [], lib_paths: [], tools: [@tools_test_linker], objects: ['build/foo.o'], libraries: [] }
1063
1071
  )
1064
1072
 
1065
1073
  @executor.stage_build_executables( @state )
1066
1074
  end
1067
1075
 
1076
+ # GH #1368: a library rebuilt under the same name changed nothing a link registered
1077
+ it "registers the library files its libraries resolve to, as files and as meta" do
1078
+ allow(@dependinator).to receive(:stale?).and_return( false )
1079
+ allow(@configurator).to receive(:project_config_hash).and_return( { libraries_test: ['x'] } )
1080
+ stub_const( 'LIBRARIES_SYSTEM', ['m', 'fusion'] )
1081
+
1082
+ expect(@library_resolver).to receive(:link_files).with( ['x', 'm', 'fusion'] ).and_return( ['vendor/lib/libx.a', 'build/deps/libfusion.so.1'] )
1083
+ expect(@dependinator).to receive(:register).with(
1084
+ 'build/a_test.out',
1085
+ files: ['build/foo.o', 'vendor/lib/libx.a', 'build/deps/libfusion.so.1'],
1086
+ meta: hash_including( libraries: ['vendor/lib/libx.a', 'build/deps/libfusion.so.1'] )
1087
+ )
1088
+
1089
+ @executor.stage_build_executables( @state )
1090
+ end
1091
+
1092
+ # Staleness checks only the objects registered this build, so an object dropped from the
1093
+ # link changes nothing they see. The list itself, as meta, does change (a source deleted,
1094
+ # excluded, or moved out of the configured paths).
1095
+ it "registers the objects it links as meta, as a plain list" do
1096
+ allow(@dependinator).to receive(:stale?).and_return( false )
1097
+ @testable.objects = FileList.new.tap { |list| list << 'build/foo.o' }
1098
+
1099
+ expect(@dependinator).to receive(:register).with( 'build/a_test.out', files: ['build/foo.o'], meta: hash_including( objects: ['build/foo.o'] ) ) do |_, files:, meta:|
1100
+ expect(meta[:objects]).to be_an_instance_of( Array )
1101
+ end
1102
+
1103
+ @executor.stage_build_executables( @state )
1104
+ end
1105
+
1068
1106
  it "logs a summary line stating how many executables were recalled from cache" do
1069
1107
  allow(@dependinator).to receive(:stale?).and_return( false )
1070
1108
 
@@ -1658,7 +1696,7 @@ describe TestBuildExecutor do
1658
1696
  include_as( 'sub/MockFoo.h' )
1659
1697
 
1660
1698
  expect(@generator).to receive(:generate_mock)
1661
- .with( hash_including( output_path: 'build/test/mocks/sub', folder: 'sub' ) )
1699
+ .with( hash_including( output_path: 'build/test/mocks/sub', mock_root: 'build/test/mocks', folder: 'sub' ) )
1662
1700
 
1663
1701
  @executor.stage_generate_mocks( @state )
1664
1702
  end
@@ -1685,6 +1723,30 @@ describe TestBuildExecutor do
1685
1723
  @executor.stage_generate_mocks( @state )
1686
1724
  end
1687
1725
 
1726
+ # The path handling audit around GH #1370: a folder that climbs must not carry the
1727
+ # mock out of its test's mocks directory
1728
+ it "G14: keeps the mock below its test's mocks directory for a folder that climbs" do
1729
+ include_as( '../x/MockFoo.h' )
1730
+
1731
+ expect(@generator).to receive(:generate_mock) do |args|
1732
+ expect(args[:output_path].split( '/' )).to_not include( '..' )
1733
+ end
1734
+
1735
+ @executor.stage_generate_mocks( @state )
1736
+ end
1737
+
1738
+ # The compiler finds the mock as written only through CMock's root, so it must exist
1739
+ it "roots CMock as deep as a climbing folder climbs, and makes that root" do
1740
+ include_as( '../x/MockFoo.h' )
1741
+
1742
+ expect(@file_wrapper).to receive(:mkdir).with( 'build/test/mocks/_up_/x' )
1743
+ expect(@file_wrapper).to receive(:mkdir).with( 'build/test/mocks/_up_/_up_' )
1744
+ expect(@generator).to receive(:generate_mock)
1745
+ .with( hash_including( output_path: 'build/test/mocks/_up_/x', mock_root: 'build/test/mocks/_up_/_up_', folder: '../x' ) )
1746
+
1747
+ @executor.stage_generate_mocks( @state )
1748
+ end
1749
+
1688
1750
  it "passes no folder when :mock_name_include_folder is disabled" do
1689
1751
  allow(@configurator).to receive(:cmock_mock_name_include_folder).and_return( false )
1690
1752
  include_as( 'sub/MockFoo.h' )
@@ -25,6 +25,8 @@ EXTENSION_HEADER = FilenameExtension.new('.h') unless defined?(EXTENSION_
25
25
  describe TestBuildPlanner do
26
26
  before(:each) do
27
27
  @configurator = double( "Configurator" )
28
+ # Ceedling always defaults it, and the mock locator reads it
29
+ allow(@configurator).to receive(:cmock_mock_name_include_folder).and_return( true )
28
30
  @loginator = double( "Loginator" )
29
31
  @reportinator = double( "Reportinator" )
30
32
  @batchinator = double( "Batchinator" )
@@ -167,6 +169,7 @@ describe TestBuildPlanner do
167
169
 
168
170
  context "with a non-Partial mocked header" do
169
171
  before(:each) do
172
+ allow(@file_path_utils).to receive(:form_mock_output_path) { |root, subdir| subdir.to_s.empty? ? root : File.join( root, subdir ) }
170
173
  mock = MockInclude.new( 'MockFoo.h' )
171
174
  allow(@test_context_extractor).to receive(:lookup_mock_header_includes_list)
172
175
  .with( 'test/TestFoo.c' ).and_return( [mock] )
@@ -204,6 +207,45 @@ describe TestBuildPlanner do
204
207
  end
205
208
  end
206
209
 
210
+ # Two headers directly in two include roots mirror alike, and only their #include
211
+ # folders tell their mocks apart
212
+ it "keeps two same-named mocks whose #include folders alone differ" do
213
+ allow(@test_context_extractor).to receive(:lookup_mock_header_includes_list)
214
+ .with( 'test/TestFoo.c' ).and_return( [MockInclude.new( 'a/MockFoo.h' ), MockInclude.new( 'b/MockFoo.h' )] )
215
+ allow(@configurator).to receive(:cmock_mock_prefix).and_return( 'Mock' )
216
+ allow(@configurator).to receive(:project_use_test_preprocessor_mocks).and_return( false )
217
+ allow(@file_finder).to receive(:resolve_mock).with( 'a/MockFoo.h', collection: [], complain: :error ).and_return( ['inc/a/foo.h', ''] )
218
+ allow(@file_finder).to receive(:resolve_mock).with( 'b/MockFoo.h', collection: [], complain: :error ).and_return( ['inc/b/foo.h', ''] )
219
+ allow(@file_path_utils).to receive(:form_preprocessed_file_filepath).and_return( 'unused' )
220
+
221
+ @planner.stage_determine_files( @state )
222
+
223
+ expect(@testable.mocks.keys).to contain_exactly( :'a/MockFoo', :'b/MockFoo' )
224
+ expect(@testable.mocks.values.map( &:source )).to contain_exactly( 'inc/a/foo.h', 'inc/b/foo.h' )
225
+ end
226
+
227
+ # Nothing in the loop over a test's mocks changes its search paths, so one collection
228
+ # serves every mock. Validation lists the whole search paths, mock directories included.
229
+ it "builds one mock header collection for all of a test's real mocks" do
230
+ mocks = %w[MockFoo.h MockBar.h MockBaz.h].map { |name| MockInclude.new( name ) }
231
+ allow(@test_context_extractor).to receive(:lookup_mock_header_includes_list).with( 'test/TestFoo.c' ).and_return( mocks )
232
+ allow(@configurator).to receive(:cmock_mock_prefix).and_return( 'Mock' )
233
+ allow(@configurator).to receive(:project_use_test_preprocessor_mocks).and_return( false )
234
+ allow(@file_finder).to receive(:resolve_mock) { |mock, **| ["src/#{mock.delete_prefix( 'Mock' )}", ''] }
235
+ allow(@file_path_utils).to receive(:form_preprocessed_file_filepath).and_return( 'unused' )
236
+ allow(@file_path_utils).to receive(:form_mock_output_path) { |root, subdir| subdir.to_s.empty? ? root : File.join( root, subdir ) }
237
+ @testable.search_paths = ['build/test/mocks/a_test', 'src']
238
+ @testable.mock_search_paths = ['build/test/mocks/a_test']
239
+
240
+ collections = 0
241
+ allow(@include_pathinator).to receive(:ordered_header_files).with( ['src'] ) { collections += 1; ['src/Foo.h'] }
242
+
243
+ @planner.stage_determine_files( @state )
244
+
245
+ expect(@testable.mocks.length).to eq( 3 )
246
+ expect(collections).to eq( 1 )
247
+ end
248
+
207
249
  context "with a Partial mocked header" do
208
250
  before(:each) do
209
251
  mock = MockInclude.new( 'Mockceedling_partial_foo_interface.h' )
@@ -217,6 +259,15 @@ describe TestBuildPlanner do
217
259
 
218
260
  # A Partial mock has no real header to resolve against, so its directory comes
219
261
  # from the generated #include rather than from a mirrored source location.
262
+ it "builds no mock header collection, having no real header to resolve" do
263
+ @testable.search_paths = ['build/test/mocks/a_test', 'src']
264
+ @testable.mock_search_paths = ['build/test/mocks/a_test']
265
+
266
+ expect(@include_pathinator).to_not receive(:ordered_header_files).with( ['src'] )
267
+
268
+ @planner.stage_determine_files( @state )
269
+ end
270
+
220
271
  it "stays flat when its generated #include names no directory" do
221
272
  expect(@file_finder).to_not receive(:resolve_mock)
222
273
 
@@ -431,6 +482,23 @@ describe TestBuildPlanner do
431
482
  expect( captured ).to include('build/test/mocks/a_test/drivers/spi/Mockconfig.c')
432
483
  end
433
484
 
485
+ it "compiles a mock whose #include folder climbs from where it is written, below the mock root" do
486
+ allow(@configurator).to receive(:cmock_mock_name_include_folder).and_return( true )
487
+ @testable.mocks = {
488
+ :'x/Mockconfig' => TestInvokerTypes::MockDetails.new( name: 'Mockconfig', path: 'x', partial: false, include_path: '../x/Mockconfig.h' )
489
+ }
490
+
491
+ captured = nil
492
+ allow(@file_path_utils).to receive(:form_test_build_objects_filelist) do |_path, files, **_kw|
493
+ captured ||= files
494
+ []
495
+ end
496
+
497
+ @planner.stage_determine_artifacts( @state )
498
+
499
+ expect( captured ).to include('build/test/mocks/a_test/_up_/x/Mockconfig.c')
500
+ end
501
+
434
502
  # A mock and a generated Partial both sit below their own generated root rather than a
435
503
  # configured source root, so nothing in the standard roots mirrors them. Without those
436
504
  # roots, two same-named mocks or modules collapse onto one object filename.
@@ -484,7 +552,9 @@ describe TestBuildPlanner do
484
552
  allow(@test_source_file_directive_resolver).to receive(:resolve)
485
553
  .with( 'test/TestFoo.c', :test ).and_return( [['src/Foo.c'], {}] )
486
554
  allow(@test_context_extractor).to receive(:lookup_source_includes_list)
487
- .with( 'test/TestFoo.c' ).and_return( ['src/Foo.c'] )
555
+ .with( 'test/TestFoo.c' ).and_return( [UserInclude.new( 'src/Foo.c' )] )
556
+ allow(@file_finder).to receive(:find_build_input_file)
557
+ .with( filepath: 'src/Foo.c', complain: :ignore, context: :test, test: 'a_test' ).and_return( 'src/Foo.c' )
488
558
  end
489
559
 
490
560
  it "resolves it as a source to compile but excludes its object from what gets linked" do
@@ -494,6 +564,25 @@ describe TestBuildPlanner do
494
564
  expect(@testable.objects).to_not include( 'src/Foo.o' )
495
565
  end
496
566
  end
567
+
568
+ # A test #includes a source by the name its own #include spells, and that source's object
569
+ # sits at the source's path, not the #include's
570
+ context "when a test #includes a source by its name alone" do
571
+ before(:each) do
572
+ allow(@test_source_file_directive_resolver).to receive(:resolve)
573
+ .with( 'test/TestFoo.c', :test ).and_return( [['src/Foo.c'], {}] )
574
+ allow(@test_context_extractor).to receive(:lookup_source_includes_list)
575
+ .with( 'test/TestFoo.c' ).and_return( [UserInclude.new( 'Foo.c' )] )
576
+ allow(@file_finder).to receive(:find_build_input_file)
577
+ .with( filepath: 'Foo.c', complain: :ignore, context: :test, test: 'a_test' ).and_return( 'src/Foo.c' )
578
+ end
579
+
580
+ it "excludes the object of the source it names from what gets linked" do
581
+ @planner.stage_determine_artifacts( @state )
582
+
583
+ expect(@testable.objects).to_not include( 'src/Foo.o' )
584
+ end
585
+ end
497
586
  end
498
587
 
499
588
  describe "#stage_flatten_objects_list" do
@@ -696,7 +785,20 @@ describe TestBuildPlanner do
696
785
 
697
786
  result = @planner.collect_test_framework_sources( true )
698
787
 
699
- expect(result).to include( 'test/support/unity_helper' )
788
+ expect(result).to include( 'test/support/unity_helper.c' )
789
+ end
790
+
791
+ # The path handling audit around GH #1370: :unity_helper_path names the helper's header,
792
+ # and its source is what compiles
793
+ it "G25: compiles the source beside a Unity helper named by its header" do
794
+ allow(@configurator).to receive(:project_use_mocks).and_return( true )
795
+ allow(@configurator).to receive(:cmock_unity_helper_path).and_return( ['test/support/unity_helper.h'] )
796
+ allow(@file_wrapper).to receive(:exist?).with( 'test/support/unity_helper.c' ).and_return( true )
797
+
798
+ result = @planner.collect_test_framework_sources( true )
799
+
800
+ expect(result).to include( 'test/support/unity_helper.c' )
801
+ expect(result).to_not include( 'test/support/unity_helper.h' )
700
802
  end
701
803
 
702
804
  it "omits a Unity helper source file that doesn't exist on disk" do