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
@@ -50,7 +50,7 @@ describe Dependencies do
50
50
  tool_validator: double( 'tool_validator' ).tap { |validator| allow(validator).to receive(:validate).and_return( true ) },
51
51
  loginator: double( 'loginator', lazy: nil ).tap { |log| allow(log).to receive(:log) { |message, *| logged << message } },
52
52
  file_path_collection_utils: double( 'file_path_collection_utils', collect_paths: [] ),
53
- file_path_utils: double( 'file_path_utils', form_release_build_list_filepath: 'list', form_test_build_map_filepath: 'map' ) }
53
+ file_path_utils: double( 'file_path_utils', form_test_build_map_filepath: 'map' ).tap { |utils| allow(utils).to receive(:form_release_build_list_filepath) { |path| path.ext( '.lst' ) } } }
54
54
  end
55
55
 
56
56
  def dependency(name = 'dep', **overrides)
@@ -245,10 +245,15 @@ describe Dependencies do
245
245
  end
246
246
 
247
247
  describe 'libraries' do
248
- it 'names every library and its directory for linking, leaving executables out' do
249
- plugin = plugin_for( dependency( artifacts: { static_libraries: ['libw.a'], dynamic_libraries: ['sub/libv.so'], executables: ['bin/tool'] } ) )
250
-
251
- expect(plugin.link_libraries).to eq( names: ['w', 'v'], paths: ['deps/dep/build', 'deps/dep/build/sub'] )
248
+ # GH #1368: a link depends on each library's exact file, which a name can't always reach
249
+ it 'names every library, its directory, and its file for linking, leaving executables out' do
250
+ plugin = plugin_for( dependency( artifacts: { static_libraries: ['libw.a'], dynamic_libraries: ['sub/libv.so.1'], executables: ['bin/tool'] } ) )
251
+
252
+ expect(plugin.link_libraries).to eq(
253
+ names: ['w', 'v.so'],
254
+ paths: ['deps/dep/build', 'deps/dep/build/sub'],
255
+ files: ['deps/dep/build/libw.a', 'deps/dep/build/sub/libv.so.1']
256
+ )
252
257
  end
253
258
  end
254
259
 
@@ -266,7 +271,7 @@ describe Dependencies do
266
271
  plugin_for( lib ).make_if_required( 'dep' )
267
272
 
268
273
  expect(generator).to have_received(:generate_object_file_c)
269
- .with( hash_including( source: 'cc/src/b.c', object: 'cc/build/b.o', search_paths: ['cc/inc'], defines: ['FAST'] ) )
274
+ .with( hash_including( source: 'cc/src/b.c', object: 'cc/build/src/b.o', search_paths: ['cc/inc'], defines: ['FAST'] ) )
270
275
  expect(executed).not_to include( a_string_starting_with( 'cd ' ) )
271
276
  end
272
277
 
@@ -274,10 +279,31 @@ describe Dependencies do
274
279
  plugin_for( lib ).make_if_required( 'dep' )
275
280
 
276
281
  expect(generator).to have_received(:generate_executable_file)
277
- .with( anything, anything, ['cc/build/a.o', 'cc/build/b.o'], [], 'cc/build/libcc.a', 'map', [], [] )
282
+ .with( anything, anything, ['cc/build/a.o', 'cc/build/src/b.o'], [], 'cc/build/libcc.a', 'map', [], [] )
278
283
  expect(file_wrapper).to have_received(:cp).with( 'cc/build/libcc.a', 'cc/out/libcc.a' )
279
284
  end
280
285
 
286
+ it 'places two same-named sources\' objects apart, by their paths below the source path' do
287
+ allow(file_wrapper).to receive(:directory_listing) do |glob|
288
+ { 'cc/**/*.c' => ['cc/a/x.c', 'cc/b/x.c'] }.fetch( glob, [] )
289
+ end
290
+
291
+ plugin_for( lib ).make_if_required( 'dep' )
292
+
293
+ expect(generator).to have_received(:generate_executable_file)
294
+ .with( anything, anything, ['cc/build/a/x.o', 'cc/build/b/x.o'], [], 'cc/build/libcc.a', 'map', [], [] )
295
+ expect(file_wrapper).to have_received(:mkdir).with( 'cc/build/a' )
296
+ expect(file_wrapper).to have_received(:mkdir).with( 'cc/build/b' )
297
+ end
298
+
299
+ # The path handling audit around GH #1370: a list file named by its source alone lands in
300
+ # the working directory
301
+ it 'writes each object\'s list file beside it' do
302
+ plugin_for( lib ).make_if_required( 'dep' )
303
+
304
+ expect(generator).to have_received(:generate_object_file_c).with( hash_including( object: 'cc/build/src/b.o', list: 'cc/build/src/b.lst' ) )
305
+ end
306
+
281
307
  it 'checks its compiler and archiver before building' do
282
308
  plugin_for( lib ).make_if_required( 'dep' )
283
309
 
@@ -132,6 +132,23 @@ describe DependencySpec do
132
132
  expect(spec.include_directories( ->( glob ) { ["#{glob}/a", "#{glob}/b"] } )).to eq( ['out/inc/**/a', 'out/inc/**/b'] )
133
133
  end
134
134
 
135
+ # The path handling audit around GH #1370: an absolute artifact names itself
136
+ it 'keeps an absolute artifact or include path as written' do
137
+ lib = File.expand_path( '/opt/sdk/lib/libsdk.a' )
138
+ inc = File.expand_path( '/opt/sdk/inc' )
139
+ spec = spec_for( paths: { artifact: 'out' }, artifacts: { static_libraries: [lib], includes: [inc] } )
140
+
141
+ expect(spec.static_libraries).to eq( [lib] )
142
+ expect(spec.include_directories( ->( _glob ) { raise 'no glob here' } )).to eq( [inc] )
143
+ end
144
+
145
+ it 'reads its configured paths as every configured path is read, `~` as the home directory' do
146
+ spec = spec_for( paths: { source: '~/deps/w\\src\\', artifact: '~/deps/w/out' } )
147
+
148
+ expect(spec.source_path).to eq( File.join( Dir.home, 'deps/w/src' ) )
149
+ expect(spec.static_libraries).to eq( [File.join( Dir.home, 'deps/w/out/libworker.a' )] )
150
+ end
151
+
135
152
  it 'treats missing artifacts as empty' do
136
153
  spec = DependencySpec.build_all( [{ name: 'tool', fetch: { method: :none } }] ).first
137
154
 
@@ -10,7 +10,7 @@ require 'yaml'
10
10
  require 'ceedling/constants'
11
11
  require 'ceedling/exceptions'
12
12
  require 'ceedling/plugins/plugin'
13
- require 'ceedling/path_mirror'
13
+ require 'ceedling/filesystem/path_mirror'
14
14
  require 'ceedling/filename_extension'
15
15
 
16
16
  PROJECT_BUILD_ROOT = 'build' unless defined?(PROJECT_BUILD_ROOT)
@@ -176,7 +176,7 @@ describe Gcov do
176
176
  let(:test_build_setup) { double('test_build_setup', context_defines: [], flags: []) }
177
177
  let(:dependinator) { double('dependinator', register: nil, register_gcc_deps_file: nil, flush: nil, mark_fresh: nil) }
178
178
  let(:generator) { double('generator', generate_object_file_c: nil) }
179
- let(:file_wrapper) { double('file_wrapper', exist?: false) }
179
+ let(:file_wrapper) { double('file_wrapper', exist?: false, mkdir: nil) }
180
180
  let(:test_invoker) { double('test_invoker') }
181
181
 
182
182
  def build_process_gcov(project_config, ivars = {})
@@ -215,7 +215,7 @@ describe Gcov do
215
215
 
216
216
  it 'logs the full, sorted filepath list and records @untested_sources without compiling' do
217
217
  gcov = build_process_gcov({ gcov_untested_sources: GCOV_UNTESTED_SOURCES_LIST })
218
- expect(loginator).to receive(:log_list).with(['src/a.c', 'src/b.c'], kind_of(String), Verbosity::COMPLAIN, LogLabels::WARNING)
218
+ expect(loginator).to receive(:log_list).with(['src/a.c', 'src/b.c'], kind_of(String), Verbosity::COMPLAIN)
219
219
  expect(generator).to_not receive(:generate_object_file_c)
220
220
  gcov.process_untested_sources(sources: ['src/b.c', 'src/a.c'])
221
221
  expect(gcov.instance_variable_get(:@untested_sources)).to eq(['src/b.c', 'src/a.c'])
@@ -243,6 +243,16 @@ describe Gcov do
243
243
  gcov.process_untested_sources(sources: ['src/a.c'])
244
244
  end
245
245
 
246
+ # An untested source's object sits at its path below the output root, in a
247
+ # directory no test build made
248
+ it 'makes the object\'s and dependencies file\'s directories before compiling' do
249
+ allow(dependinator).to receive(:stale?).and_return(true)
250
+ gcov = build_process_gcov({ gcov_untested_sources: GCOV_UNTESTED_SOURCES_COMPILE, gcov_mcdc: false })
251
+ expect(file_wrapper).to receive(:mkdir).with('build/gcov/out/src').twice.ordered
252
+ expect(generator).to receive(:generate_object_file_c).ordered
253
+ gcov.process_untested_sources(sources: ['src/a.c'])
254
+ end
255
+
246
256
  it 'compiles a stale object and marks it fresh' do
247
257
  allow(dependinator).to receive(:stale?).and_return(true)
248
258
  gcov = build_process_gcov({ gcov_untested_sources: GCOV_UNTESTED_SOURCES_COMPILE, gcov_mcdc: false })
@@ -341,6 +351,21 @@ describe Gcov do
341
351
  expect(succeeding).to have_received(:generate_reports)
342
352
  end
343
353
 
354
+ # GH #1366: every violated coverage threshold is its own entry, so each gets its own bullet
355
+ it 'registers each coverage threshold violation as its own build failure' do
356
+ failing = double('failing_reportinator', artifacts_path: nil, name: 'Gcovr', summary: '')
357
+ allow(failing).to receive(:generate_reports).and_raise(GcovrThresholdError.new(['line low', 'condition low']))
358
+
359
+ plugin_manager = double('plugin_manager')
360
+ expect(plugin_manager).to receive(:register_build_failure).with(GCOV_SYM, 'line low').ordered
361
+ expect(plugin_manager).to receive(:register_build_failure).with(GCOV_SYM, 'condition low').ordered
362
+
363
+ gcov = build_gcov({}, reports_enabled: true, configurator: configurator, ceedling: { plugin_manager: plugin_manager })
364
+ allow(gcov).to receive(:build_reportinators).and_return([failing])
365
+
366
+ gcov.generate_coverage_reports
367
+ end
368
+
344
369
  it 'logs the reportinator summary only when non-empty' do
345
370
  r_with_summary = double('r1', artifacts_path: nil, generate_reports: nil, name: 'R1', summary: 'coverage: 80%')
346
371
  r_without = double('r2', artifacts_path: nil, generate_reports: nil, name: 'R2', summary: '')
@@ -336,25 +336,30 @@ describe GcovrReportinator do
336
336
  describe '#gcovr_exec_exception?' do
337
337
  let(:reportinator) { build_reportinator({}) }
338
338
 
339
- it 'raises one exception listing every violated :fail_under_* threshold, not just the first' do
340
- opts = { fail_under_line: 90, fail_under_branch: 80 }
341
- # exit code 6 = bit 2 (line) | bit 4 (branch)
339
+ LINE_AND_CONDITION_STDERR = "(ERROR) Failed minimum line coverage (got 76.7%, minimum 100.0%)\n" +
340
+ "(ERROR) Failed minimum condition coverage (got 92.9%, minimum 100.0%)\n"
341
+
342
+ # GH #1366: a run failing line and condition coverage reported the line message twice
343
+ it 'raises one error carrying every violated threshold, each with its own gcovr message' do
344
+ opts = { fail_under_line: 100, fail_under_decision: 100 }
345
+ # exit code 10 = bit 2 (line) | bit 8 (condition or decision)
342
346
  expect {
343
- reportinator.send(:gcovr_exec_exception?, opts, 6, true, { stderr: '' })
344
- }.to raise_error(CeedlingException) { |ex|
345
- expect(ex.message).to match(/Line coverage/)
346
- expect(ex.message).to match(/Branch coverage/)
347
+ reportinator.send(:gcovr_exec_exception?, opts, 10, true, { stderr: LINE_AND_CONDITION_STDERR })
348
+ }.to raise_error(GcovrThresholdError) { |ex|
349
+ expect(ex.violations).to eq( [
350
+ 'Gcovr ⏩️ Failed minimum line coverage (got 76.7%, minimum 100.0%)',
351
+ 'Gcovr ⏩️ Failed minimum condition coverage (got 92.9%, minimum 100.0%)'
352
+ ] )
347
353
  }
348
354
  end
349
355
 
350
- it 'logs (not raises) every violation, clearing their bits, when boom is false' do
351
- opts = { fail_under_line: 90, fail_under_branch: 80 }
352
- expect(loginator).to receive(:log).with(/Line coverage/, Verbosity::COMPLAIN)
353
- expect(loginator).to receive(:log).with(/Branch coverage/, Verbosity::COMPLAIN)
354
- # exit code 6 (line bit 2 | branch bit 4) is fully explained by the two configured
355
- # thresholds above -- both bits clear, leaving no unexplained problem.
356
+ it 'logs (not raises) every violation once, clearing their bits, when boom is false' do
357
+ opts = { fail_under_line: 100, fail_under_decision: 100 }
358
+ expect(loginator).to receive(:log).with(/Failed minimum line coverage/, Verbosity::COMPLAIN).once
359
+ expect(loginator).to receive(:log).with(/Failed minimum condition coverage/, Verbosity::COMPLAIN).once
360
+ # Both bits are explained by the two configured thresholds, leaving no unexplained problem
356
361
  expect(
357
- reportinator.send(:gcovr_exec_exception?, opts, 6, false, { stderr: '' })
362
+ reportinator.send(:gcovr_exec_exception?, opts, 10, false, { stderr: LINE_AND_CONDITION_STDERR })
358
363
  ).to eq(false)
359
364
  end
360
365
 
@@ -698,39 +703,6 @@ describe GcovrReportinator do
698
703
  end
699
704
  end
700
705
 
701
- describe '#extract_gcovr_error_message' do
702
- let(:reportinator) { build_reportinator({}) }
703
-
704
- it 'returns nil for a nil shell_result' do
705
- expect( reportinator.send(:extract_gcovr_error_message, nil) ).to be_nil
706
- end
707
-
708
- it 'returns nil when stderr is empty' do
709
- expect( reportinator.send(:extract_gcovr_error_message, { stderr: '' }) ).to be_nil
710
- end
711
-
712
- it 'captures, trims, and capitalizes an (ERROR)-prefixed line' do
713
- result = reportinator.send(:extract_gcovr_error_message, { stderr: "(ERROR) something went wrong.\n" })
714
- expect(result).to eq('Something went wrong.')
715
- end
716
-
717
- it 'matches case-insensitively' do
718
- result = reportinator.send(:extract_gcovr_error_message, { stderr: "error: lowercase message\n" })
719
- expect(result).to eq('Lowercase message')
720
- end
721
-
722
- it 'uses only the first matching line among several' do
723
- stderr = "(ERROR) first problem\n(ERROR) second problem\n"
724
- result = reportinator.send(:extract_gcovr_error_message, { stderr: stderr })
725
- expect(result).to eq('First problem')
726
- end
727
-
728
- it 'returns nil when no error-prefixed line exists' do
729
- result = reportinator.send(:extract_gcovr_error_message, { stderr: "just some noise\n" })
730
- expect(result).to be_nil
731
- end
732
- end
733
-
734
706
  describe '#extract_gcovr_summary' do
735
707
  let(:reportinator) { build_reportinator({}) }
736
708
 
@@ -0,0 +1,95 @@
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 'spec_helper'
9
+ require 'ceedling/exceptions'
10
+
11
+ $: << File.expand_path('../../../../plugins/gcov/lib', __FILE__)
12
+
13
+ require 'gcovr_thresholds'
14
+
15
+ # GH #1366: every violated threshold took stderr's first (ERROR) line, so two violations
16
+ # reported the line-coverage message twice and lost the other
17
+ describe GcovrThresholds do
18
+ # gcovr 8.8, :mcdc on, --fail-under-line 100 --fail-under-decision 100 (exit 10 = 2 | 8).
19
+ # From 8.7, --fail-under-decision also checks condition coverage, so bit 8 carries two lines.
20
+ STDERR_8_8 = <<~STDERR
21
+ (ERROR) Failed minimum line coverage (got 50.0%, minimum 100.0%)
22
+ (ERROR) Failed minimum condition coverage (got 50.0%, minimum 100.0%)
23
+ (ERROR) Failed minimum decision coverage (got 0.0%, minimum 100.0%)
24
+ STDERR
25
+
26
+ def decode(opts, exitcode, stderr)
27
+ GcovrThresholds.decode( opts: opts, exitcode: exitcode, stderr: stderr )
28
+ end
29
+
30
+ it 'reports each violated threshold with its own gcovr message, once' do
31
+ violations, unexplained = decode( {fail_under_line: 100, fail_under_decision: 100}, 10, STDERR_8_8 )
32
+
33
+ expect(violations).to eq( [
34
+ 'Gcovr ⏩️ Failed minimum line coverage (got 50.0%, minimum 100.0%)',
35
+ 'Gcovr ⏩️ Failed minimum condition coverage (got 50.0%, minimum 100.0%)',
36
+ 'Gcovr ⏩️ Failed minimum decision coverage (got 0.0%, minimum 100.0%)'
37
+ ] )
38
+ expect(unexplained).to eq( 0 )
39
+ end
40
+
41
+ it 'reports a decision message alone, as gcovr before 8.7 writes it' do
42
+ stderr = "(ERROR) Failed minimum decision coverage (got 40.0%, minimum 90.0%)\n"
43
+
44
+ violations, _ = decode( {fail_under_decision: 90}, 8, stderr )
45
+
46
+ expect(violations).to eq( ['Gcovr ⏩️ Failed minimum decision coverage (got 40.0%, minimum 90.0%)'] )
47
+ end
48
+
49
+ it 'keeps gcovr\'s capitalization' do
50
+ stderr = "(ERROR) Failed minimum branch coverage (got 1.0%, minimum MC/DC 2.0%)\n"
51
+
52
+ violations, _ = decode( {fail_under_branch: 2}, 4, stderr )
53
+
54
+ expect(violations.first).to end_with( 'Failed minimum branch coverage (got 1.0%, minimum MC/DC 2.0%)' )
55
+ end
56
+
57
+ it 'capitalizes the message an older gcovr writes in lowercase' do
58
+ stderr = "(ERROR) failed minimum line coverage (got 50.0%, minimum 90.0%)\n"
59
+
60
+ violations, _ = decode( {fail_under_line: 90}, 2, stderr )
61
+
62
+ expect(violations).to eq( ['Gcovr ⏩️ Failed minimum line coverage (got 50.0%, minimum 90.0%)'] )
63
+ end
64
+
65
+ it 'falls back to a description of each violated threshold that gcovr gave no message for' do
66
+ violations, _ = decode( {fail_under_line: 90, fail_under_decision: 80, fail_under_function: 70}, 2 | 8 | 16, '' )
67
+
68
+ expect(violations).to eq( [
69
+ 'Gcovr ⏩️ Line coverage is less than the configured minimum of 90%',
70
+ 'Gcovr ⏩️ Condition or decision coverage is less than the configured minimum of 80%',
71
+ 'Gcovr ⏩️ Function coverage is less than the configured minimum of 70%'
72
+ ] )
73
+ end
74
+
75
+ it 'leaves unexplained every bit without a configured threshold' do
76
+ violations, unexplained = decode( {fail_under_line: 90}, 2 | 4 | 64, '' )
77
+
78
+ expect(violations.length).to eq( 1 )
79
+ expect(unexplained).to eq( 4 | 64 )
80
+ end
81
+
82
+ it 'reports nothing for a zero exit code, and accepts a nil stderr' do
83
+ expect( decode( {fail_under_line: 90}, 0, nil ) ).to eq( [[], 0] )
84
+ end
85
+ end
86
+
87
+ describe GcovrThresholdError do
88
+ it 'carries each violation, and joins them as its message' do
89
+ error = GcovrThresholdError.new( ['one', 'two'] )
90
+
91
+ expect(error).to be_a( CeedlingException )
92
+ expect(error.violations).to eq( ['one', 'two'] )
93
+ expect(error.message).to eq( "one\ntwo" )
94
+ end
95
+ end
@@ -105,7 +105,7 @@ describe ModuleGenerator do
105
105
  config[:module_generator_update_svn] = true
106
106
  allow(ModuleGeneratorUnity).to receive(:new).and_return( unity )
107
107
  allow(unity).to receive(:generate)
108
- expect(loginator).to receive(:log).with( /:update_svn.*deprecated/, Verbosity::COMPLAIN, LogLabels::WARNING )
108
+ expect(loginator).to receive(:log).with( /:update_svn.*deprecated/, Verbosity::COMPLAIN )
109
109
 
110
110
  plugin.create( request( 'Lair' ) )
111
111
  end
@@ -127,7 +127,7 @@ describe ModuleGenerator do
127
127
  allow(ModuleGeneratorUnity).to receive(:new).and_return( unity )
128
128
  allow(unity).to receive(:files_to_operate_on).with( 'Lair' ).and_return( [{ path: Pathname.new( 'src/Lair.c' ) }, planned_test( 'test/test_Lair.c' )] )
129
129
  allow(unity).to receive(:generate)
130
- expect(loginator).to receive(:log).with( /`test\/test_Lair.c`.*`#ifdef TEST`.*:defines/m, Verbosity::COMPLAIN, LogLabels::WARNING ).once
130
+ expect(loginator).to receive(:log).with( /`test\/test_Lair.c`.*`#ifdef TEST`.*:defines/m, Verbosity::COMPLAIN ).once
131
131
 
132
132
  plugin.create( request( 'Lair' ) )
133
133
  end
@@ -138,7 +138,7 @@ describe ModuleGenerator do
138
138
  allow(ModuleGeneratorUnity).to receive(:new).and_return( unity )
139
139
  allow(unity).to receive(:files_to_operate_on).and_return( [planned_test( 'test/test_Lair.c' )] )
140
140
  allow(unity).to receive(:generate)
141
- expect(loginator).to receive(:log).with( /`#if TEST`/, Verbosity::COMPLAIN, LogLabels::WARNING )
141
+ expect(loginator).to receive(:log).with( /`#if TEST`/, Verbosity::COMPLAIN )
142
142
 
143
143
  plugin.create( request( 'Lair' ) )
144
144
  end
@@ -156,7 +156,7 @@ describe ModuleGenerator do
156
156
  config.merge!( collection_all_source: ['lib/Lair.c'], collection_all_headers: [], collection_all_tests: [] )
157
157
  allow(ModuleGeneratorUnity).to receive(:new).and_return( unity )
158
158
  allow(unity).to receive(:files_to_operate_on).with( 'Lair' ).and_return( [{ path: Pathname.new( 'src/Lair.c' ) }] )
159
- expect(loginator).to receive(:log).with( /`src\/Lair.c`.*`lib\/Lair.c`/, Verbosity::COMPLAIN, LogLabels::WARNING )
159
+ expect(loginator).to receive(:log).with( /`src\/Lair.c`.*`lib\/Lair.c`/, Verbosity::COMPLAIN )
160
160
  expect(unity).to receive(:generate).with( 'Lair' )
161
161
 
162
162
  plugin.create( request( 'Lair' ) )
@@ -192,7 +192,7 @@ describe ModuleGenerator do
192
192
  resolved_defines['test/test_widget.c'] = []
193
193
  allow(file_wrapper).to receive(:exist?).with( 'test/test_widget.c' ).and_return( false )
194
194
  allow(file_wrapper).to receive(:write)
195
- expect(loginator).to receive(:log).with( /`test\/test_widget.c`.*`#ifdef TEST`/m, Verbosity::COMPLAIN, LogLabels::WARNING )
195
+ expect(loginator).to receive(:log).with( /`test\/test_widget.c`.*`#ifdef TEST`/m, Verbosity::COMPLAIN )
196
196
 
197
197
  plugin.test( request( 'widget' ) )
198
198
  end
@@ -53,7 +53,7 @@ describe ReportBuildWarningsLog do
53
53
  describe '#post_build' do
54
54
  it 'logs that no warnings were produced and never touches the filesystem when nothing was collected' do
55
55
  expect(@loginator).to receive(:log).with("Build produced no warnings.\n")
56
- expect(@file_wrapper).not_to receive(:mkdir)
56
+ expect(@file_wrapper).not_to receive(:mkdir_for)
57
57
  expect(@file_wrapper).not_to receive(:write)
58
58
 
59
59
  @plugin.post_build(0)
@@ -91,7 +91,7 @@ describe ReportBuildWarningsLog do
91
91
  collection: WarningCollector.new.warnings( "foo.c:3:5: warning: unused variable 'x'\nbar.c:9:1: warning: missing return\n" )
92
92
  }
93
93
 
94
- expect(@file_wrapper).to receive(:mkdir).with(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test'))
94
+ allow(@file_wrapper).to receive(:mkdir_for) { |path| path }
95
95
  expect(@file_wrapper).to receive(:write).with(
96
96
  File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test', 'warnings.log'),
97
97
  "foo.c:3:5: warning: unused variable 'x'\nbar.c:9:1: warning: missing return\n"
@@ -138,7 +138,7 @@ describe ReportBuildWarningsLog do
138
138
 
139
139
  def written
140
140
  files = []
141
- allow(@file_wrapper).to receive(:mkdir)
141
+ allow(@file_wrapper).to receive(:mkdir_for) { |path| path }
142
142
  allow(@file_wrapper).to receive(:write) { |path, _| files << path }
143
143
  return files
144
144
  end
@@ -37,12 +37,21 @@ describe ReportLogWriterPlugin do
37
37
 
38
38
  describe '#artifact_filepath' do
39
39
  it 'ensures the per-context artifact directory exists and returns the joined filepath' do
40
- expect(@file_wrapper).to receive(:mkdir).with(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test'))
40
+ expect(@file_wrapper).to receive(:mkdir_for).with(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test', 'warnings.log')) { |path| path }
41
41
 
42
42
  filepath = @plugin.send(:artifact_filepath, :test, 'warnings.log')
43
43
 
44
44
  expect(filepath).to eq(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test', 'warnings.log'))
45
45
  end
46
+
47
+ # GH #1364: a nested test's log sits in its own subdirectory
48
+ it 'ensures the directory of a filename with subdirectories exists' do
49
+ expect(@file_wrapper).to receive(:mkdir_for).with(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test', 'test/unit/test_foo.raw.log')) { |path| path }
50
+
51
+ filepath = @plugin.send(:artifact_filepath, :test, 'test/unit/test_foo.raw.log')
52
+
53
+ expect(filepath).to eq(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test', 'test/unit/test_foo.raw.log'))
54
+ end
46
55
  end
47
56
 
48
57
  describe '#flush_log' do
@@ -72,7 +72,7 @@ describe ReportTestsRawOutputLog do
72
72
  describe '#post_build' do
73
73
  it 'logs that no extra output was produced and never touches the filesystem when nothing was collected' do
74
74
  expect(@loginator).to receive(:log).with("Tests produced no extra console output.\n")
75
- expect(@file_wrapper).not_to receive(:mkdir)
75
+ expect(@file_wrapper).not_to receive(:mkdir_for)
76
76
  expect(@file_wrapper).not_to receive(:write)
77
77
 
78
78
  @plugin.post_build(0)
@@ -86,7 +86,7 @@ describe ReportTestsRawOutputLog do
86
86
  'TestFoo' => ["Debug: entering state X\n", "Debug: leaving state X\n"]
87
87
  }
88
88
 
89
- expect(@file_wrapper).to receive(:mkdir).with(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test'))
89
+ allow(@file_wrapper).to receive(:mkdir_for) { |path| path }
90
90
  expect(@file_wrapper).to receive(:write).with(
91
91
  File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test', 'TestFoo.raw.log'),
92
92
  "Debug: entering state X\nDebug: leaving state X\n"
@@ -94,5 +94,16 @@ describe ReportTestsRawOutputLog do
94
94
 
95
95
  @plugin.post_build(0)
96
96
  end
97
+
98
+ # GH #1364: a nested test's log sits beneath its name
99
+ it "makes the directory a nested test's log goes in" do
100
+ @plugin.instance_variable_get(:@raw_output)[:test] = { 'test/unit/test_foo' => ["Debug\n"] }
101
+ filepath = File.join(PROJECT_BUILD_ARTIFACTS_ROOT, 'test', 'test/unit/test_foo.raw.log')
102
+
103
+ expect(@file_wrapper).to receive(:mkdir_for).with(filepath) { |path| path }
104
+ expect(@file_wrapper).to receive(:write).with(filepath, "Debug\n")
105
+
106
+ @plugin.post_build(0)
107
+ end
97
108
  end
98
109
  end
@@ -8,6 +8,7 @@
8
8
  require 'spec_helper'
9
9
  require 'stringio'
10
10
  require 'ceedling/plugins/plugin'
11
+ require 'ceedling/filesystem/file_path_utils'
11
12
 
12
13
  $: << File.expand_path('../../../../plugins/report_tests_teamcity_stdout/lib', __FILE__)
13
14
  require 'report_tests_teamcity_stdout'
@@ -23,7 +24,10 @@ describe ReportTestsTeamcityStdout do
23
24
  @plugin.instance_variable_set(:@flowid_count, 0)
24
25
  @plugin.instance_variable_set(:@suites, {})
25
26
  @plugin.instance_variable_set(:@stream, @stream)
26
- @plugin.instance_variable_set(:@ceedling, { plugin_reportinator: @plugin_reportinator })
27
+ @plugin.instance_variable_set(:@ceedling, {
28
+ plugin_reportinator: @plugin_reportinator,
29
+ file_path_utils: FilePathUtils.new( configurator: nil, file_wrapper: nil )
30
+ })
27
31
  end
28
32
 
29
33
  describe '#escape' do
@@ -38,8 +42,23 @@ describe ReportTestsTeamcityStdout do
38
42
  @plugin.post_test('test/TestFoo.c')
39
43
 
40
44
  lines = @stream.string.lines
41
- expect(lines[0]).to eq("##teamcity[testSuiteStarted name='TestFoo' flowId='1']\n")
42
- expect(lines[1]).to eq("##teamcity[testSuiteFinished name='TestFoo' flowId='1']\n")
45
+ expect(lines[0]).to eq("##teamcity[testSuiteStarted name='test/TestFoo' flowId='1']\n")
46
+ expect(lines[1]).to eq("##teamcity[testSuiteFinished name='test/TestFoo' flowId='1']\n")
47
+ end
48
+
49
+ # GH #1364: same-named tests in different directories are different suites
50
+ it 'names each suite by its test, so same-named tests are told apart' do
51
+ @plugin.pre_test('test/a/test_x.c')
52
+ @plugin.pre_test('test/b/test_x.c')
53
+
54
+ expect(@stream.string).to include("testSuiteStarted name='test/a/test_x'", "testSuiteStarted name='test/b/test_x'")
55
+ end
56
+
57
+ it 'escapes a suite name, as every TeamCity value is' do
58
+ @plugin.pre_test("test/o'neil/test_x.c")
59
+ @plugin.post_test("test/o'neil/test_x.c")
60
+
61
+ expect(@stream.string.lines).to all( include("name='test/o|'neil/test_x'") )
43
62
  end
44
63
 
45
64
  it 'gives each successive test its own flow id' do
@@ -108,6 +127,61 @@ describe ReportTestsTeamcityStdout do
108
127
  end
109
128
  end
110
129
 
130
+ # GH #1369: a reserved character in a test's path, case name, or failure location
131
+ # closed the quoted value early, and TeamCity misread or dropped the message
132
+ describe '#post_test_fixture_execute escaping' do
133
+ RESERVED_DIR = "test/o'neil|x"
134
+ RESERVED_CASE = 'test_value(1, [2])'
135
+ ESCAPED_NAME = "test.test/o|'neil||x/TestFoo.test_value(1, |[2|])"
136
+
137
+ before(:each) do
138
+ @plugin.instance_variable_get(:@suites)['test/TestFoo.c'] = { flowid: 1 }
139
+ end
140
+
141
+ def run_with(results)
142
+ allow(@plugin_reportinator).to receive(:assemble_test_results).and_return(
143
+ { total_time: 1.0, counts: { passed: 1, failed: 1 }, successes: [], failures: [], ignores: [] }.merge( results )
144
+ )
145
+ @plugin.post_test_fixture_execute(context: :test, test_filepath: 'test/TestFoo.c', result_file: 'x')
146
+ end
147
+
148
+ def source
149
+ { dirname: RESERVED_DIR, basename: 'TestFoo.c', file: "#{RESERVED_DIR}/TestFoo.c" }
150
+ end
151
+
152
+ it 'escapes the names of testStarted and testFinished for a passing test' do
153
+ run_with( successes: [ { source: source, collection: [ { test: RESERVED_CASE } ] } ] )
154
+
155
+ expect(@stream.string).to include(
156
+ "##teamcity[testStarted name='#{ESCAPED_NAME}' flowId='1']\n",
157
+ "##teamcity[testFinished name='#{ESCAPED_NAME}' duration='500' flowId='1']\n"
158
+ )
159
+ end
160
+
161
+ it 'escapes the name and details of testFailed, and the names around it' do
162
+ run_with( failures: [ { source: source, collection: [ { test: RESERVED_CASE, line: 7, message: 'm' } ] } ] )
163
+
164
+ expect(@stream.string).to include(
165
+ "##teamcity[testStarted name='#{ESCAPED_NAME}' flowId='1']\n",
166
+ "##teamcity[testFailed name='#{ESCAPED_NAME}' message='m' details='File: test/o|'neil||x/TestFoo.c Line: 7' flowId='1']\n",
167
+ "##teamcity[testFinished name='#{ESCAPED_NAME}' duration='500' flowId='1']\n"
168
+ )
169
+ end
170
+
171
+ it 'escapes the name of testIgnored' do
172
+ run_with( ignores: [ { source: source, collection: [ { test: RESERVED_CASE } ] } ] )
173
+
174
+ expect(@stream.string).to eq( "##teamcity[testIgnored name='#{ESCAPED_NAME}' flowId='1']\n" )
175
+ end
176
+
177
+ it 'keeps each message on one physical line when a name holds a line break' do
178
+ run_with( ignores: [ { source: source, collection: [ { test: "test_a\r\ntest_b" } ] } ] )
179
+
180
+ expect(@stream.string.lines.length).to eq( 1 )
181
+ expect(@stream.string).to include( 'TestFoo.test_a|r|ntest_b' )
182
+ end
183
+ end
184
+
111
185
  # A TeamCity service message must stay on one physical line
112
186
  describe '#escape' do
113
187
  it 'escapes a real newline as |n, keeping the whole message on one physical output line' do
@@ -9,7 +9,7 @@ require 'spec_helper'
9
9
  require 'ceedling/tool_executor'
10
10
  require 'ceedling/constants'
11
11
  require 'ceedling/exceptions'
12
- require 'ceedling/file_path_utils'
12
+ require 'ceedling/filesystem/file_path_utils'
13
13
 
14
14
  PROJECT_BUILD_ROOT = 'build' unless defined?(PROJECT_BUILD_ROOT)
15
15
  PROJECT_BUILD_ARTIFACTS_ROOT = 'artifacts' unless defined?(PROJECT_BUILD_ARTIFACTS_ROOT)
@@ -8,6 +8,8 @@
8
8
  require 'spec_helper'
9
9
  require 'ceedling/constants'
10
10
 
11
+ # Ceedling runtime path constants sanitize_constants.rb needs at require time
12
+ PROJECT_BUILD_ROOT = 'build' unless defined?(PROJECT_BUILD_ROOT)
11
13
  PROJECT_BUILD_ARTIFACTS_ROOT = 'build/artifacts' unless defined?(PROJECT_BUILD_ARTIFACTS_ROOT)
12
14
 
13
15
  $: << File.expand_path('../../../../plugins/sanitize/lib', __FILE__)
@@ -15,7 +17,7 @@ require 'sanitize_findings'
15
17
  require 'sanitize_records'
16
18
 
17
19
  describe SanitizeRecords do
18
- let(:file_wrapper) { double('file_wrapper', mkdir: nil, write: nil) }
20
+ let(:file_wrapper) { double('file_wrapper', mkdir: nil, write: nil, mkdir_for: nil) }
19
21
  let(:loginator) { double('loginator', log: nil) }
20
22
  let(:records) { described_class.new( file_wrapper: file_wrapper, loginator: loginator, reports: [] ) }
21
23
  let(:log) { described_class.log_filepath( :sanitize, 'test_a' ) }
@@ -37,7 +37,7 @@ describe Sanitize do
37
37
 
38
38
  let(:loginator) { double('loginator', log: nil) }
39
39
  let(:dependinator) { double('dependinator', register: nil) }
40
- let(:file_wrapper) { double('file_wrapper', write: nil, mkdir: nil) }
40
+ let(:file_wrapper) { double('file_wrapper', write: nil, mkdir: nil, mkdir_for: nil) }
41
41
  let(:plugin_manager) { double('plugin_manager', register_build_failure: nil) }
42
42
  let(:tracker) { double('rake_invocation_tracker', invoked?: true) }
43
43
  let(:tool_validator) { double('tool_validator', validate: nil) }