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
@@ -7,15 +7,22 @@
7
7
 
8
8
  require 'rubygems'
9
9
  require 'rake' # for adding ext() method to string
10
+ require 'set'
10
11
  require 'ceedling/exceptions'
11
- require 'ceedling/path_matcher'
12
- require 'ceedling/path_mirror'
13
- require 'ceedling/file_path_utils'
12
+ require 'ceedling/filesystem/path_matcher'
13
+ require 'ceedling/filesystem/path_mirror'
14
+ require 'ceedling/filesystem/file_path_utils'
15
+ require 'ceedling/filesystem/project_paths'
16
+ require 'ceedling/filesystem/build_subpath'
14
17
 
15
18
  class FileFinder
16
19
 
17
20
  constructor :configurator, :file_finder_helper, :file_path_utils, :file_wrapper, :yaml_wrapper
18
21
 
22
+ def setup
23
+ @test_names_lock = Mutex.new
24
+ end
25
+
19
26
 
20
27
  # `collection`, when given, is a caller's own already-ordered, per-test header list
21
28
  # (see IncludePathinator#ordered_header_files) reflecting that one test's real search-path
@@ -59,7 +66,7 @@ class FileFinder
59
66
  source = find_header_input_for_mock(mock, collection: collection, complain: complain)
60
67
  return [nil, nil] if source.nil?
61
68
 
62
- subdir = PathMirror.relative_subdir(source, @configurator.paths_test + @configurator.paths_support + @configurator.paths_include)
69
+ subdir = PathMirror.relative_subdir_from_clean_roots( source, @configurator.collection_mirror_roots_headers )
63
70
  return [source, subdir]
64
71
  end
65
72
 
@@ -72,11 +79,10 @@ class FileFinder
72
79
  # compilation step downstream to catch a wrong guess the way there is for a header
73
80
  # or source file resolved during a build.
74
81
  #
75
- # A task name has no file of its own to anchor a `..` against, unlike an #include or
76
- # TEST_SOURCE_FILE() entry -- resolving with no anchor surfaces that as a specific,
77
- # named error rather than falling through to a generic file-not-found further down.
82
+ # A task name is relative to the working directory, as every collected path is, so a
83
+ # leading `..` names a test outside the project.
78
84
  def find_test_file_from_name(name)
79
- name = PathMatcher.resolve_relative(name)
85
+ name = PathMatcher.resolve_relative(name, anchor: '')
80
86
  return find_first_candidate(name, @configurator.extension_source, @configurator.collection_all_tests, :error, strict: true)
81
87
  end
82
88
 
@@ -107,9 +113,8 @@ class FileFinder
107
113
  # Some legacy files can include naming conventions like <name>.##.<ext> for versioning.
108
114
  # If we use .ext() below we'll clobber the dotted portion of the filename
109
115
 
110
- # Generated test runners -- a flat test's own runner is searched for in the shared flat
111
- # runners directory; only a nested test's own mirrored subdirectory scopes the search
112
- # further, so two same-named tests' runners are never mistaken for each other
116
+ # Generated test runners -- searched for in the runners directory of this test's name,
117
+ # so two same-named tests' runners are never mistaken for each other
113
118
  if (!release) and
114
119
  (source_file =~ /^#{Regexp.escape(@configurator.project_test_file_prefix)}.+#{Regexp.escape(@configurator.test_runner_file_suffix)}$/)
115
120
  _source_file = source_file + EXTENSION_CORE_SOURCE
@@ -140,9 +145,7 @@ class FileFinder
140
145
  found_file =
141
146
  @file_finder_helper.find_file_in_collection(
142
147
  _source_file,
143
- @file_wrapper.directory_listing(
144
- File.join(mocks_root, ('**/*' + EXTENSION_CORE_SOURCE))
145
- ),
148
+ own_generated_files( @configurator.cmock_mock_path, test_context ),
146
149
  complain)
147
150
 
148
151
  # Generated partials -- same per-test scoping rationale as mocks, above
@@ -157,10 +160,7 @@ class FileFinder
157
160
  _source_file = source_file + EXTENSION_CORE_SOURCE
158
161
  _source_file = File.join( subdir, _source_file ) unless subdir.empty?
159
162
 
160
- collection =
161
- @file_wrapper.directory_listing(
162
- File.join(partials_root, ('**/*' + EXTENSION_CORE_SOURCE))
163
- )
163
+ collection = own_generated_files( @configurator.project_test_partials_path, test_context )
164
164
 
165
165
  # Ambiguity is detected here rather than rescued from the matching below. One
166
166
  # matching implementation stays in force, and the message is composed from the
@@ -180,12 +180,11 @@ class FileFinder
180
180
  @configurator.collection_existing_test_build_input,
181
181
  complain)
182
182
 
183
- # The test file's own object, resolving back to its own source -- go directly by this
184
- # object's own test identity rather than searching the whole project's test collection
185
- # by bare basename, which is exactly the kind of query that's ambiguous the moment
186
- # another same-named test exists elsewhere
183
+ # The test file's own object resolves to the test it names. A name is the test's path,
184
+ # so the lookup is exact. Searching by name instead is ambiguous wherever one test's
185
+ # name ends another test's path.
187
186
  elsif (!release) and !test_context.empty? and (source_file == File.basename( test_context ))
188
- found_file = find_test_file_from_name( test_context )
187
+ found_file = test_files_by_name[test_context]
189
188
 
190
189
  end
191
190
 
@@ -202,37 +201,16 @@ class FileFinder
202
201
  # We save the existence handling until the end.
203
202
  #
204
203
 
205
- # A module-under-test or release source's object mirrors that source's own subdirectory
206
- # below whichever configured root it came from -- recovering that mirrored subdirectory
207
- # here lets the query go straight to the matching source, rather than a bare basename
208
- # that's ambiguous the moment another same-named source exists elsewhere.
209
- query_file = mirrored_query(filepath, release: release, test: test, context: context)
204
+ # A module-under-test or release object sits at its source's own path below its build
205
+ # root, so that path recovers the one source, rather than a bare basename that is
206
+ # ambiguous the moment another same-named source exists elsewhere.
207
+ query_file, exact = mirrored_query(filepath, release: release, test: test, context: context)
210
208
 
211
- # Assembly files for release build
212
- if release and @configurator.release_build_use_assembly
213
- found_file = try_extensions(query_file, @configurator.extension_assembly, @configurator.collection_release_build_input)
214
-
215
- # Assembly files for test build
216
- elsif (!release) and @configurator.test_build_use_assembly
217
- found_file = try_extensions(query_file, @configurator.extension_assembly, @configurator.collection_existing_test_build_input)
218
- end
219
-
220
- if !found_file.nil?
221
- return found_file
222
- end
223
-
224
- # Release build C files
225
- if release
226
- found_file = try_extensions(query_file, @configurator.extension_source, @configurator.collection_release_build_input)
227
-
228
- # Test build C files
229
- else
230
- found_file = try_extensions(query_file, @configurator.extension_source, @configurator.collection_existing_test_build_input)
231
- end
209
+ found_file = find_compiled_input( query_file, release: release, exact: exact )
232
210
 
233
211
  if found_file.nil?
234
- # Every name actually attempted above, laid out for the reader in the same order they
235
- # were tried, so a missing-file complaint names every filename ceedling went looking for.
212
+ # Every name actually attempted, in the order tried, so a missing-file complaint
213
+ # names every filename ceedling went looking for.
236
214
  tried = @configurator.extension_source.map { |ext| query_file + ext }
237
215
  tried += @configurator.extension_assembly.map { |ext| query_file + ext } if @configurator.release_build_use_assembly
238
216
  @file_finder_helper.handle_missing_file(tried.join(' or '), complain)
@@ -265,6 +243,24 @@ class FileFinder
265
243
 
266
244
  private
267
245
 
246
+ # A C or assembly build input for `query`, assembly first where the build uses it. An
247
+ # exact query is the file's own path, matched as such before any tail match.
248
+ def find_compiled_input(query, release:, exact:)
249
+ collection = release ? @configurator.collection_release_build_input : @configurator.collection_existing_test_build_input
250
+
251
+ compiled_extensions( release ).each do |extension|
252
+ found = (exact && exact_file( query, extension, collection )) || try_extensions( query, extension, collection )
253
+ return found unless found.nil?
254
+ end
255
+
256
+ return nil
257
+ end
258
+
259
+ def compiled_extensions(release)
260
+ assembly = release ? @configurator.release_build_use_assembly : @configurator.test_build_use_assembly
261
+ return assembly ? [@configurator.extension_assembly, @configurator.extension_source] : [@configurator.extension_source]
262
+ end
263
+
268
264
  # Translates a generated-Partial ambiguity into the test author's own terms. The query
269
265
  # here is a generated filename under a build directory, neither of which the author
270
266
  # typed, so reporting them offers nothing to act on. Candidates render as extensionless
@@ -299,74 +295,133 @@ class FileFinder
299
295
  raise CeedlingException.new( message )
300
296
  end
301
297
 
302
- # A test object's build directory identity mirrors the configured test root a source
303
- # file lives under (e.g. `unit/test_foo` for `test/unit/test_foo.c`, plain `test_foo` for
304
- # a flat file). Recovering that identity from a build artifact's own path lets a lookup
305
- # go straight to the one test it belongs to, rather than searching the whole project by
306
- # bare basename -- exactly the query shape that's ambiguous when another same-named test
307
- # exists elsewhere.
298
+ # A test object's build directory is its test's name, the test's path from the project
299
+ # root (e.g. `test/unit/test_foo` for `test/unit/test_foo.c`). Recovering that name from
300
+ # a build artifact's own path lets a lookup go straight to the one test it belongs to,
301
+ # rather than searching the whole project by bare basename -- exactly the query shape
302
+ # that's ambiguous when another same-named test exists elsewhere.
308
303
  #
309
304
  # A filepath that isn't under the test build root at all (a source-tree-relative query
310
305
  # from an #include or TEST_SOURCE_FILE(), rather than a build artifact's own path) has no
311
306
  # test identity to recover -- empty string, never a directory name that could coincidentally
312
307
  # match some unrelated file's own basename.
308
+ #
309
+ # A test's build directory also holds its objects at their sources' own paths, so the
310
+ # name is the longest one of a known test that the directory begins with.
313
311
  def test_context_of(filepath)
314
- dir = File.dirname( filepath )
315
- root = @configurator.project_test_build_output_path
316
- return dir.start_with?( root + '/' ) ? dir[(root.length + 1)..] : ''
312
+ subdir = ProjectPaths.subdir_below( @configurator.project_test_build_output_path, File.dirname( filepath ) )
313
+ return '' if subdir.nil?
314
+
315
+ parts = subdir.split( '/' )
316
+ parts.length.downto( 1 ) do |count|
317
+ name = parts.first( count ).join( '/' )
318
+ return name if test_files_by_name.key?( name )
319
+ end
320
+
321
+ return ''
317
322
  end
318
323
 
319
- # A flat test's own runner lives in the shared flat runners directory; only a nested
320
- # test's own mirrored subdirectory (excluding its own basename) narrows the search
321
- # further -- mirroring FilePathUtils#form_test_runners_path's own directory-forming rule.
322
- # Takes the test identity already recovered by the caller rather than a raw filepath, so
323
- # it isn't independently re-deriving what the caller already has in hand.
324
+ # A test's runner lives where FilePathUtils#form_test_runners_path puts it. Takes the test
325
+ # identity the caller already recovered rather than a raw filepath.
324
326
  def runner_scope_dir(test_context)
325
- subdir = File.dirname( test_context )
326
- root = @configurator.project_test_runners_path
327
- return subdir == '.' ? root : File.join(root, subdir)
327
+ return @file_path_utils.form_test_runners_path( test_context )
328
328
  end
329
329
 
330
- # A release or module-under-test object's own path mirrors its source's subdirectory
331
- # below whichever configured root it came from, rooted at the release build output path
332
- # or (given the current test's identity) that test's own build path. Recovering that
333
- # mirrored subdirectory and combining it with the bare basename recreates a query that
334
- # identifies the same one source PathMirror originally mirrored it from.
330
+ # Every test file by its name. Names are paths, and a build stops before its first lookup
331
+ # if two tests in it share one. Built once, on first use, since build threads share this
332
+ # finder.
333
+ def test_files_by_name
334
+ @test_names_lock.synchronize do
335
+ @test_files_by_name ||= @configurator.collection_all_tests.to_h { |test| [@file_path_utils.form_test_name( test ), test.to_s] }
336
+ end
337
+ end
338
+
339
+ # For each test's name, the names of the tests nested beneath it (`test/test_foo/test_bar`
340
+ # beneath `test/test_foo`)
341
+ def nested_test_names
342
+ names = test_files_by_name.keys
343
+ @test_names_lock.synchronize do
344
+ @nested_test_names ||= nest_names( names )
345
+ end
346
+ end
347
+
348
+ # Files each name under every other name that is one of its parent directories
349
+ def nest_names(names)
350
+ known = names.to_set
351
+ nested = Hash.new { |hash, name| hash[name] = [] }
352
+
353
+ names.each do |name|
354
+ parts = name.split( '/' )
355
+ (1...parts.length).each do |count|
356
+ parent = parts.first( count ).join( '/' )
357
+ nested[parent] << name if known.include?( parent )
358
+ end
359
+ end
360
+
361
+ return nested
362
+ end
363
+
364
+ # A test's generated mocks or Partials, beneath its own directory below `base`. A test
365
+ # whose name continues this one's (`test/test_foo/test_bar` after `test/test_foo`) has its
366
+ # directory nested here, and the files in it are that test's own.
367
+ def own_generated_files(base, test_context)
368
+ listing = @file_wrapper.directory_listing( FilePathUtils.glob( File.join( base, test_context ), '**/*' + EXTENSION_CORE_SOURCE ) )
369
+ nested = nested_test_names.fetch( test_context, [] ).map { |name| File.join( base, name ) + '/' }
370
+ return listing if nested.empty?
371
+
372
+ return listing.reject { |file| nested.any? { |dir| file.start_with?( dir ) } }
373
+ end
374
+
375
+ # The query for the source of a release or module-under-test object, and whether it is
376
+ # exact. Such an object sits at its source's own path below the release output root, or
377
+ # below the build path of the test it belongs to, so decoding that path names the source.
335
378
  #
336
- # A filepath that isn't under the project's build root at all is a source-tree-relative
337
- # query in its own right -- straight from an #include or a TEST_SOURCE_FILE() directive,
338
- # say. Such a path keeps whatever disambiguating path it carries rather than being
339
- # collapsed to a bare basename, reduced to its own namespace below a configured include
340
- # root when one contains it (see header_root_relative_query below).
379
+ # A filepath outside the build root is a source-tree-relative query in its own right,
380
+ # from an #include or a TEST_SOURCE_FILE() directive, say. It keeps whatever path it
381
+ # carries, reduced to its namespace below a configured include root when one contains it
382
+ # (see header_root_relative_query below).
341
383
  #
342
- # A filepath that IS somewhere under the build root, but either belongs to a build
343
- # context this method doesn't specifically know how to mirror (a plugin's own object
344
- # output, e.g. build/valgrind/out/..., rather than release or test) or lacks enough to
345
- # peel back a known per-test root (test build input queried without a test identity),
346
- # falls back to the bare basename -- exactly what every caller has always seen for those
347
- # cases.
384
+ # A filepath below the build root in a context this method doesn't know (a plugin's own
385
+ # object output, e.g. build/valgrind/out/...), or test build input queried without a test
386
+ # identity, falls back to the bare basename.
348
387
  def mirrored_query(filepath, release:, test:, context:)
349
- dir = File.dirname(filepath)
350
- build_root = @configurator.project_build_root
388
+ return [source_tree_query( filepath ), false] unless ProjectPaths.contains?( @configurator.project_build_root, filepath )
351
389
 
352
- unless dir == build_root || dir.start_with?(build_root + '/')
353
- query = header_root_relative_query( filepath )
354
- return query.nil? ? filepath.ext('') : query
355
- end
390
+ root = placement_root( release, test, context )
391
+ subdir = root.nil? ? nil : ProjectPaths.subdir_below( root, File.dirname( filepath ) )
392
+ basename = File.basename( filepath ).ext('')
393
+ return [basename, false] if subdir.nil? || subdir.empty?
394
+
395
+ return [File.join( BuildSubpath.decode( subdir ), basename ), true]
396
+ end
356
397
 
357
- basename = File.basename(filepath).ext('')
398
+ def source_tree_query(filepath)
399
+ query = header_root_relative_query( filepath )
400
+ return query.nil? ? filepath.ext('') : query
401
+ end
358
402
 
359
- root =
360
- if release
361
- @configurator.project_release_build_output_path
362
- elsif test
363
- @file_path_utils.form_test_build_path(test, context: context)
364
- end
403
+ # The root a release or test object is placed below, or nil where none is known
404
+ def placement_root(release, test, context)
405
+ return @configurator.project_release_build_output_path if release
406
+ return nil if test.nil?
365
407
 
366
- return basename if root.nil?
408
+ return @file_path_utils.form_test_build_path( test, context: context )
409
+ end
410
+
411
+ # The file of `collection` at exactly `query` plus one of `extension`'s spellings, or nil.
412
+ # Compared by absolute path, through an index built once per collection, since build
413
+ # threads share this finder.
414
+ def exact_file(query, extension, collection)
415
+ index = @test_names_lock.synchronize do
416
+ (@exact_indexes ||= {}.compare_by_identity)[collection] ||= collection.to_h { |file| [ProjectPaths.absolute( file ), file.to_s] }
417
+ end
418
+
419
+ extension.to_a.each do |ext|
420
+ found = index[ProjectPaths.absolute( query + ext )]
421
+ return found unless found.nil?
422
+ end
367
423
 
368
- subdir = PathMirror.relative_subdir(filepath, [root])
369
- return subdir.empty? ? basename : File.join(subdir, basename)
424
+ return nil
370
425
  end
371
426
 
372
427
  # The subdirectory a generated file's query carries, mirroring the module it belongs to.
@@ -399,9 +454,9 @@ class FileFinder
399
454
 
400
455
  return '' if dir == '.'
401
456
 
402
- # Matched at a path boundary. A directory merely beginning with the build root's own
403
- # name is an ordinary relative directory and still names its module.
404
- return '' if dir == root || dir.start_with?( root + '/' )
457
+ # Compared as directories. A directory merely beginning with the build root's own name
458
+ # is an ordinary relative directory and still names its module.
459
+ return '' if ProjectPaths.contains?( root, dir )
405
460
 
406
461
  return dir
407
462
  end
@@ -415,7 +470,7 @@ class FileFinder
415
470
  # Only the root is dropped. The namespace is preserved, since that is what keeps two
416
471
  # same-basename modules apart.
417
472
  def header_root_relative_query(filepath)
418
- subdir = PathMirror.relative_subdir_if_rooted( filepath, @configurator.paths_include )
473
+ subdir = PathMirror.relative_subdir_if_rooted_in_clean_roots( filepath, @configurator.collection_mirror_roots_include )
419
474
 
420
475
  return nil if subdir.nil?
421
476
 
@@ -8,12 +8,16 @@
8
8
  require 'fileutils'
9
9
  require 'ceedling/constants' # for Verbosity enumeration
10
10
  require 'ceedling/exceptions'
11
- require 'ceedling/path_matcher'
11
+ require 'ceedling/filesystem/path_matcher'
12
12
 
13
13
  class FileFinderHelper
14
14
 
15
15
  constructor :loginator
16
16
 
17
+ def setup
18
+ @noticed = []
19
+ @notice_mutex = Mutex.new
20
+ end
17
21
 
18
22
  def find_file_in_collection(filename, file_list, complain)
19
23
  found = PathMatcher.match(filename, file_list)
@@ -25,7 +29,8 @@ class FileFinderHelper
25
29
  # As `#find_file_in_collection`, but never raises on ambiguity -- a query matching
26
30
  # more than one file resolves to the first entry in `file_list`'s own order (which
27
31
  # already reflects real project path priority), with an ℹ️ NOTICE naming what else
28
- # matched so the choice isn't silent.
32
+ # matched so the choice isn't silent. Every test that names the file makes the same
33
+ # choice, so a build notes it once.
29
34
  def resolve_file_in_collection(filename, file_list, complain)
30
35
  found, others = PathMatcher.resolve(filename, file_list)
31
36
  return not_found_in_collection(filename, file_list, complain) if found.nil?
@@ -34,7 +39,8 @@ class FileFinderHelper
34
39
  msg = "Multiple files matched '#{filename}' but chose '#{found}' by search-path priority. " \
35
40
  "Other candidates: #{others.join(', ')}. Add more path to '#{filename}' at its point " \
36
41
  "of reference to select a different file."
37
- @loginator.log( msg, Verbosity::COMPLAIN, LogLabels::NOTICE )
42
+ first = @notice_mutex.synchronize { !@noticed.include?( msg ) && @noticed << msg }
43
+ @loginator.log( msg, Verbosity::COMPLAIN, LogLabels::NOTICE ) if first
38
44
  end
39
45
 
40
46
  return found
@@ -45,35 +51,14 @@ class FileFinderHelper
45
51
  raise "No path list provided for #{description} search" if path_list.nil?
46
52
  return pathname if path_list.include?(pathname)
47
53
 
48
- # Determine the closest match by looking for matching path segments, especially paths ENDING the same
49
- best_match_index = 0
50
- best_match_value = 0
51
- reverse_original_pieces = pathname.split(/(?:\\|\/)/).reverse
52
- path_list.each_with_index do |p,i|
53
- reverse_match_pieces = p.split(/(?:\\|\/)/).reverse
54
- #
55
- num = reverse_original_pieces.zip(reverse_match_pieces).inject(0){|s,v| v[0] == v[1] ? s+3 : s}
56
- num = reverse_original_pieces.inject(num){|s,v| reverse_match_pieces.include?(v) ? s+1 : s}
57
- if num > best_match_value
58
- best_match_index = i
59
- best_match_value = num
60
- end
61
- end
54
+ # The closest match shares the most path segments, especially those ENDING the same. The
55
+ # first of equally close matches wins, and the first path when none shares a segment.
56
+ pieces = reversed_pieces( pathname )
57
+ scores = path_list.map { |path| match_score( pieces, reversed_pieces( path ) ) }
62
58
 
63
- # If none of the options were a good match, handle to the best of our ability
64
- if (best_match_value == 0) && (reverse_original_pieces.length > 0)
65
- case (complain)
66
- when :error
67
- raise CeedlingException.new( "Found no path `#{pathname}` in #{description} search paths." )
68
- when :warn
69
- warning = "Found no path `#{pathname}` in #{description} search paths."
70
- @loginator.log( warning, Verbosity::COMPLAIN )
71
- when :ignore
72
- # nothing further to do
73
- end
74
- end
59
+ complain_of_no_path( "Found no path `#{pathname}` in #{description} search paths.", complain ) if scores.max.to_i.zero? and !pieces.empty?
75
60
 
76
- return path_list[best_match_index]
61
+ return path_list[best_index( scores )]
77
62
  end
78
63
 
79
64
  def handle_missing_file(filename, complain)
@@ -122,6 +107,26 @@ class FileFinderHelper
122
107
  @loginator.log( warning, Verbosity::COMPLAIN )
123
108
  end
124
109
 
125
- end
110
+ def reversed_pieces(path)
111
+ return path.split( /(?:\\|\/)/ ).reverse
112
+ end
113
+
114
+ # Three for each trailing segment matched in place, and one for each segment found anywhere
115
+ def match_score(pieces, candidate)
116
+ in_place = pieces.zip( candidate ).count { |piece, other| piece == other }
117
+ return (in_place * 3) + pieces.count { |piece| candidate.include?( piece ) }
118
+ end
119
+
120
+ # The first of the highest scores, or the first when none scores
121
+ def best_index(scores)
122
+ best = scores.max.to_i
123
+ return best.zero? ? 0 : scores.index( best )
124
+ end
126
125
 
126
+ def complain_of_no_path(message, complain)
127
+ raise CeedlingException.new( message ) if complain == :error
127
128
 
129
+ @loginator.log( message, Verbosity::COMPLAIN ) if complain == :warn
130
+ end
131
+
132
+ end