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
@@ -0,0 +1,198 @@
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
+ # How tests are named, across project layouts with same-named tests (GH #1364). A project's YAML runs through the real
9
+ # configuration pipeline in a temporary project, so test paths and collections come from
10
+ # real globs. A real FilePathUtils names each test, and a real TestBuildSetup prepares its
11
+ # directories, as stage 1 of a build does.
12
+
13
+ require 'spec_helper'
14
+ require 'configurator_integration_helper'
15
+ require 'ceedling/filesystem/file_path_utils'
16
+ require 'ceedling/filesystem/file_finder'
17
+ require 'ceedling/filesystem/file_finder_helper'
18
+ require 'ceedling/test_invoker/test_build_setup'
19
+ require 'ceedling/test_invoker/test_invoker_types'
20
+
21
+ describe 'Test naming (integration)' do
22
+ include ConfiguratorIntegrationHelpers
23
+
24
+ # Runs each test of a batch in turn, in place of the thread pool
25
+ class TestNamingBatchinator
26
+ def exec(things:, **_options)
27
+ things.each { |thing| yield thing }
28
+ end
29
+ end
30
+
31
+ # name => [test paths, `:paths ↳ :test` entries, `:files ↳ :test` entries]
32
+ TEST_NAMING_LAYOUTS = {
33
+ 'flat and nested under one root' => [%w[test/test_calc.c test/unit/test_calc.c], %w[test/**], []],
34
+ 'the same subdirectory under two roots' => [%w[test/unit/test_calc.c extra/unit/test_calc.c], %w[test/unit/** extra/**], []],
35
+ 'overlapping roots' => [%w[test/test_calc.c test/unit/test_calc.c], %w[test/** test/unit/**], []],
36
+ 'the same layout under two roots' => [%w[test/foo/test_x.c extra/foo/test_x.c], %w[test/** extra/**], []],
37
+ 'a :files test outside every root' => [%w[test/test_calc.c other/test_calc.c], %w[test/**], %w[other/test_calc.c]]
38
+ }.freeze
39
+
40
+ def project_yaml(test_paths, test_files)
41
+ return <<~YAML
42
+ :project:
43
+ :build_root: build
44
+ :paths:
45
+ :test: #{test_paths.inspect}
46
+ :source: [src/**]
47
+ :include: [src/**]
48
+ :files:
49
+ :test: #{test_files.inspect}
50
+ YAML
51
+ end
52
+
53
+ def project_files(tests)
54
+ files = { 'src/calc.c' => "int calc(void) { return 1; }\n", 'src/calc.h' => "int calc(void);\n" }
55
+ tests.each { |test| files[test] = "#include \"unity.h\"\nvoid test_it(void) {}\n" }
56
+ return files
57
+ end
58
+
59
+ # Stage 1 of a build of `tests`, with real naming and real directories
60
+ def prepare(configurator, tests)
61
+ file_wrapper = FileWrapper.new( loginator: double( 'loginator', log: nil ), verbosinator: double( 'verbosinator', should_output?: false ) )
62
+ file_path_utils = FilePathUtils.new( configurator: configurator, file_wrapper: file_wrapper )
63
+
64
+ collaborators = TestBuildSetup.constructor_keys.to_h { |key| [key, double( key.to_s )] }
65
+ setup = TestBuildSetup.new( collaborators.merge(
66
+ configurator: configurator, file_wrapper: file_wrapper, file_path_utils: file_path_utils, batchinator: TestNamingBatchinator.new
67
+ ) )
68
+
69
+ state = TestInvokerTypes::PipelineState.new( tests: tests, testables: {}, context: :test, options: [], lock: Mutex.new )
70
+ setup.stage_prepare_build_paths( state )
71
+ return [state, file_path_utils]
72
+ end
73
+
74
+ def finder(configurator, file_path_utils)
75
+ loginator = double( 'loginator', log: nil )
76
+ return FileFinder.new(
77
+ configurator: configurator, file_finder_helper: FileFinderHelper.new( loginator: loginator ), file_path_utils: file_path_utils,
78
+ file_wrapper: FileWrapper.new( loginator: loginator, verbosinator: double( 'verbosinator', should_output?: false ) ), yaml_wrapper: nil
79
+ )
80
+ end
81
+
82
+ # Each test's own object resolves to that test, by name and from its object path alone
83
+ def expect_own_objects_resolve(configurator, state, file_path_utils)
84
+ finder = finder( configurator, file_path_utils )
85
+ state.testables.each_value do |testable|
86
+ object = File.join( testable.paths[:build], File.basename( testable.filepath ).ext( '.o' ) )
87
+ expect(finder.find_build_input_file( filepath: object, context: :test, test: testable.name )).to eq( testable.filepath )
88
+ expect(finder.find_build_input_file( filepath: object, context: :test )).to eq( testable.filepath )
89
+ end
90
+ end
91
+
92
+ TEST_NAMING_LAYOUTS.each do |layout, (tests, test_paths, test_files)|
93
+ context "with #{layout}" do
94
+ it 'names each test by its path, inside the build directory, with results where summaries look' do
95
+ in_temp_project( project_files( tests ) ) do
96
+ configurator = configure( project_yaml( test_paths, test_files ) )[:configurator]
97
+ expect(configurator.collection_all_tests).to match_array( tests )
98
+
99
+ state, file_path_utils = prepare( configurator, configurator.collection_all_tests )
100
+
101
+ expect(state.testables.keys.map( &:to_s )).to match_array( tests.map { |test| test.ext( '' ) } )
102
+
103
+ build = File.expand_path( 'build' ) + '/'
104
+ state.testables.each_value do |testable|
105
+ testable.paths.each_value { |path| expect(File.expand_path( path )).to start_with( build ) }
106
+ end
107
+
108
+ expected = state.testables.values.map { |testable| file_path_utils.form_pass_results_filepath( testable.paths[:results], testable.filepath ) }
109
+ expect(file_path_utils.form_pass_results_filelist( configurator.project_test_results_path, tests )).to match_array( expected )
110
+ expect(expected.uniq.size).to eq( tests.size )
111
+
112
+ expect_own_objects_resolve( configurator, state, file_path_utils )
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ # One test's name continues another's, so its directories nest inside the other's
119
+ context 'with a test directory named like a test beside it' do
120
+ it "keeps each test's mocks its own" do
121
+ tests = %w[test/test_foo.c test/test_foo/test_bar.c]
122
+ in_temp_project( project_files( tests ) ) do
123
+ configurator = configure( project_yaml( %w[test/**], [] ) )[:configurator]
124
+ state, file_path_utils = prepare( configurator, configurator.collection_all_tests )
125
+ expect_own_objects_resolve( configurator, state, file_path_utils )
126
+
127
+ mock = "#{configurator.cmock_mock_prefix}widget"
128
+ state.testables.each_value do |testable|
129
+ FileUtils.mkdir_p( testable.paths[:mocks] )
130
+ File.write( File.join( testable.paths[:mocks], "#{mock}.c" ), "\n" )
131
+ end
132
+
133
+ finder = finder( configurator, file_path_utils )
134
+ state.testables.each_value do |testable|
135
+ object = File.join( testable.paths[:build], "#{mock}.o" )
136
+ expect(finder.find_build_input_file( filepath: object, context: :test, test: testable.name )).to eq( File.join( testable.paths[:mocks], "#{mock}.c" ) )
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ context 'with tests outside the project' do
143
+ def outside_files
144
+ return project_files( [] ).transform_keys { |path| File.join( 'app', path ) }.merge(
145
+ 'shared/test/unit/test_s.c' => "void test_it(void) {}\n",
146
+ 'other/test_o.c' => "void test_it(void) {}\n"
147
+ )
148
+ end
149
+
150
+ it 'keeps their names and directories inside the build directory' do
151
+ in_temp_project( outside_files ) do
152
+ Dir.chdir( 'app' ) do
153
+ configurator = configure( project_yaml( %w[../shared/test/**], %w[../other/test_o.c] ) )[:configurator]
154
+
155
+ state, file_path_utils = prepare( configurator, configurator.collection_all_tests )
156
+
157
+ expect(state.testables.keys.map( &:to_s )).to match_array( %w[_up_/shared/test/unit/test_s _up_/other/test_o] )
158
+ expect_own_objects_resolve( configurator, state, file_path_utils )
159
+ build = File.expand_path( 'build' ) + '/'
160
+ state.testables.each_value do |testable|
161
+ testable.paths.each_value { |path| expect(File.expand_path( path )).to start_with( build ) }
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ # A project directory named like the marker a test outside the project is named by keeps
168
+ # a name of its own
169
+ it 'names a test below a project directory called `_up_` apart from one above the project' do
170
+ files = outside_files.merge( 'app/_up_/other/test_o.c' => "void test_it(void) {}\n" )
171
+
172
+ in_temp_project( files ) do
173
+ Dir.chdir( 'app' ) do
174
+ configurator = configure( project_yaml( %w[../shared/test/** _up_/**], %w[../other/test_o.c] ) )[:configurator]
175
+
176
+ state, _file_path_utils = prepare( configurator, ['_up_/other/test_o.c', '../other/test_o.c'] )
177
+
178
+ expect(state.testables.keys.map( &:to_s )).to match_array( %w[__up_/other/test_o _up_/other/test_o] )
179
+ end
180
+ end
181
+ end
182
+
183
+ it 'stops a build that has two tests sharing one name, naming both' do
184
+ files = outside_files.merge( 'app/test/test_d.c' => "void test_it(void) {}\n", 'app/test/test_d.cpp' => "void test_it(void) {}\n" )
185
+
186
+ in_temp_project( files ) do
187
+ Dir.chdir( 'app' ) do
188
+ yaml = project_yaml( %w[test/**], [] ) + ":extension:\n :source: ['.c', '.cpp']\n"
189
+ configurator = configure( yaml )[:configurator]
190
+
191
+ expect { prepare( configurator, ['test/test_d.c'] ) }.to raise_error( CeedlingException ) { |error|
192
+ expect(error.message).to include( 'test/test_d.c', 'test/test_d.cpp', '`test/test_d`' )
193
+ }
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,166 @@
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
+ # Checking TEST_SOURCE_FILE() entries against a real project. Real test files are scanned by
9
+ # the real context extractor, and the real resolver looks each entry up through the real
10
+ # file finder in a temporary project configured by the real configuration pipeline.
11
+ #
12
+ # Each test's entries are checked once, under that test's own name, with test preprocessing
13
+ # on or off, so a misspelled entry stops the build naming the test that holds it (GH #1364).
14
+
15
+ require 'spec_helper'
16
+ require 'configurator_integration_helper'
17
+ require 'ceedling/parsing_parcels'
18
+ require 'ceedling/includes/include_factory'
19
+ require 'ceedling/filesystem/file_finder'
20
+ require 'ceedling/filesystem/file_finder_helper'
21
+ require 'ceedling/filesystem/file_path_utils'
22
+ require 'ceedling/test_context_extractor'
23
+ require 'ceedling/test_invoker/test_source_file_directive_resolver'
24
+ require 'ceedling/test_invoker/test_build_setup'
25
+ require 'ceedling/test_invoker/test_invoker_types'
26
+
27
+ describe 'TEST_SOURCE_FILE() checks (integration)' do
28
+ include ConfiguratorIntegrationHelpers
29
+
30
+ # Runs each test of a batch in turn, in place of the thread pool
31
+ class TestSourceFileChecksBatchinator
32
+ def exec(things:, **_options)
33
+ things.each { |key, value| yield key, value }
34
+ end
35
+ end
36
+
37
+ def test_file(entries)
38
+ lines = entries.map { |entry| "TEST_SOURCE_FILE(\"#{entry}\")\n" }.join
39
+ return "#include \"unity.h\"\n#{lines}void test_it(void) {}\n"
40
+ end
41
+
42
+ def project_files
43
+ return {
44
+ 'src/calc.c' => "int calc(void) { return 1; }\n",
45
+ 'src/calc.h' => "int calc(void);\n",
46
+ 'src/a/dup.c' => "int dup(void) { return 1; }\n",
47
+ 'lib/a/dup.c' => "int dup(void) { return 2; }\n",
48
+ 'lib/extra.c' => "int extra(void) { return 7; }\n",
49
+ 'test/test_m1.c' => test_file( ['calc.c'] ),
50
+ 'test/test_m2.c' => test_file( ['calc.c'] ),
51
+ 'test/unit/test_m3.c' => test_file( ['../../lib/extra.c'] )
52
+ }
53
+ end
54
+
55
+ def project_yaml(preprocessor: :none)
56
+ return <<~YAML
57
+ :project:
58
+ :build_root: build
59
+ :use_test_preprocessor: :#{preprocessor}
60
+ :paths:
61
+ :test: [test/**]
62
+ :source: [src/**, lib/**]
63
+ :include: [src/**]
64
+ YAML
65
+ end
66
+
67
+ def objects
68
+ graph = configure( project_yaml )
69
+ configurator = graph[:configurator]
70
+ loginator = graph[:loginator]
71
+ file_wrapper = graph[:file_wrapper]
72
+
73
+ file_path_utils = FilePathUtils.new( configurator: configurator, file_wrapper: file_wrapper )
74
+ file_finder = FileFinder.new(
75
+ configurator: configurator, file_finder_helper: FileFinderHelper.new( loginator: loginator ),
76
+ file_path_utils: file_path_utils, file_wrapper: file_wrapper, yaml_wrapper: graph[:yaml_wrapper]
77
+ )
78
+ extractor = TestContextExtractor.new(
79
+ configurator: configurator, parsing_parcels: ParsingParcels.new, include_factory: IncludeFactory.new( configurator: configurator ),
80
+ partializer_config: double( 'partializer_config' ).as_null_object, file_path_utils: file_path_utils, file_wrapper: file_wrapper,
81
+ yaml_wrapper: graph[:yaml_wrapper], loginator: loginator
82
+ )
83
+ resolver = TestSourceFileDirectiveResolver.new(
84
+ test_context_extractor: extractor, file_finder: file_finder, configurator: configurator, loginator: loginator
85
+ )
86
+
87
+ return { configurator: configurator, file_path_utils: file_path_utils, file_finder: file_finder, extractor: extractor, resolver: resolver }
88
+ end
89
+
90
+ def scan(extractor, tests)
91
+ tests.each { |test| extractor.collect_simple_context_from_file( test, nil, TestContextExtractor::Context::BUILD_DIRECTIVE_SOURCE_FILES ) }
92
+ end
93
+
94
+ it "passes a test's real entries, including one reached through .." do
95
+ in_temp_project( project_files ) do
96
+ o = objects
97
+ tests = %w[test/test_m1.c test/test_m2.c test/unit/test_m3.c]
98
+ scan( o[:extractor], tests )
99
+
100
+ tests.each do |test|
101
+ expect { o[:resolver].validate!( test: test.ext( '' ), test_filepath: test, context: :test ) }.to_not raise_error
102
+ end
103
+ end
104
+ end
105
+
106
+ it 'names the test that holds a missing entry' do
107
+ in_temp_project( project_files.merge( 'test/test_m2.c' => test_file( ['calc_typo.c'] ) ) ) do
108
+ o = objects
109
+ scan( o[:extractor], %w[test/test_m1.c test/test_m2.c] )
110
+
111
+ expect { o[:resolver].validate!( test: 'test/test_m1', test_filepath: 'test/test_m1.c', context: :test ) }.to_not raise_error
112
+ expect { o[:resolver].validate!( test: 'test/test_m2', test_filepath: 'test/test_m2.c', context: :test ) }.to raise_error(
113
+ CeedlingException, %r{'calc_typo\.c' specified with TEST_SOURCE_FILE\(\) in test/test_m2 cannot be found}
114
+ )
115
+ end
116
+ end
117
+
118
+ it 'stops for a misspelled removal entry, which otherwise leaves its file in the build' do
119
+ in_temp_project( project_files.merge( 'test/test_m1.c' => test_file( ['-:clac.c'] ) ) ) do
120
+ o = objects
121
+ scan( o[:extractor], %w[test/test_m1.c] )
122
+
123
+ expect { o[:resolver].validate!( test: 'test/test_m1', test_filepath: 'test/test_m1.c', context: :test ) }.to raise_error(
124
+ CeedlingException, /'clac\.c'.*cannot be found/
125
+ )
126
+ end
127
+ end
128
+
129
+ it 'notes an entry matching several files once, however many tests name it' do
130
+ files = project_files.merge( %w[test/test_m1.c test/test_m2.c test/unit/test_m3.c].to_h { |test| [test, test_file( ['a/dup.c'] )] } )
131
+
132
+ in_temp_project( files ) do
133
+ o = objects
134
+ tests = %w[test/test_m1.c test/test_m2.c test/unit/test_m3.c]
135
+ scan( o[:extractor], tests )
136
+
137
+ tests.each do |test|
138
+ o[:resolver].validate!( test: test.ext( '' ), test_filepath: test, context: :test )
139
+ o[:resolver].resolve( test, :test )
140
+ end
141
+
142
+ expect( logged.grep( /Multiple files matched 'a\/dup\.c'/ ).size ).to eq( 1 )
143
+ end
144
+ end
145
+
146
+ # Without test preprocessing, stage 2 scans the entries and checks them
147
+ it 'checks each test under its own name in stage 2 without test preprocessing' do
148
+ in_temp_project( project_files.merge( 'test/unit/test_m3.c' => test_file( ['nowhere.c'] ) ) ) do
149
+ o = objects
150
+ setup = TestBuildSetup.new( TestBuildSetup.constructor_keys.to_h { |key| [key, double( key.to_s ).as_null_object] }.merge(
151
+ configurator: o[:configurator], batchinator: TestSourceFileChecksBatchinator.new, test_context_extractor: o[:extractor],
152
+ file_path_utils: o[:file_path_utils], file_finder: o[:file_finder], test_source_file_directive_resolver: o[:resolver],
153
+ file_wrapper: FileWrapper.new( loginator: double( 'loginator', log: nil ), verbosinator: double( 'verbosinator', should_output?: false ) ),
154
+ dependinator: double( 'dependinator', register: nil, stale?: true, mark_fresh: nil ),
155
+ reportinator: double( 'reportinator', generate_progress: '', generate_skip_summary: nil )
156
+ ) )
157
+ tests = %w[test/test_m1.c test/test_m2.c test/unit/test_m3.c]
158
+ state = TestInvokerTypes::PipelineState.new( tests: tests, testables: {}, context: :test, options: [], lock: Mutex.new )
159
+ setup.stage_prepare_build_paths( state )
160
+
161
+ expect { setup.stage_collect_test_context( state ) }.to raise_error(
162
+ CeedlingException, %r{'nowhere\.c' specified with TEST_SOURCE_FILE\(\) in test/unit/test_m3 cannot be found}
163
+ )
164
+ end
165
+ end
166
+ end
@@ -17,7 +17,7 @@ require 'spec_helper'
17
17
  require 'spec_integration_helper'
18
18
  require 'fileutils'
19
19
  require 'ceedling/tool_validator'
20
- require 'ceedling/file_wrapper'
20
+ require 'ceedling/filesystem/file_wrapper'
21
21
  require 'ceedling/system_wrapper'
22
22
  require 'ceedling/ruby_expandinator'
23
23
  require 'ceedling/filename_extension'
@@ -0,0 +1,84 @@
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
+ # GH #1371: `upgrade` never refreshed documentation that `new --docs` exported, because it
9
+ # looked for a file exports stopped writing in 1.1.0. For a 1.0.x export, which it did
10
+ # find, it removed all of docs/, the project's own files included.
11
+ #
12
+ # These run the real launcher against real projects. Nothing compiles, so no toolchain is
13
+ # needed.
14
+
15
+ require 'spec_helper'
16
+ require 'tmpdir'
17
+ require 'fileutils'
18
+ require 'open3'
19
+ require 'rbconfig'
20
+
21
+ describe '`upgrade` and exported documentation' do
22
+ REPO = File.expand_path( '../..', __dir__ )
23
+ UNITY_PAGE = File.join( 'unity', 'UnityGettingStartedGuide.md' )
24
+
25
+ def ceedling(*args)
26
+ stdout, stderr, status = Open3.capture3(
27
+ {'CEEDLING_DECORATORS' => 'false'}, RbConfig.ruby, File.join( REPO, 'bin', 'ceedling' ), *args, chdir: @tmp
28
+ )
29
+ raise "`ceedling #{args.join( ' ' )}` failed:\n#{stdout}#{stderr}" unless status.success?
30
+ end
31
+
32
+ def docs(*entry)
33
+ return File.join( @tmp, 'proj', 'docs', *entry )
34
+ end
35
+
36
+ def write(entry, text='')
37
+ FileUtils.mkdir_p( File.dirname( docs( entry ) ) )
38
+ File.write( docs( entry ), text )
39
+ end
40
+
41
+ around(:each) do |example|
42
+ Dir.mktmpdir( 'upgrade_docs' ) { |dir| @tmp = dir; example.run }
43
+ end
44
+
45
+ it 'refreshes an export of 1.1.0 or later and keeps the project\'s own files' do
46
+ ceedling( 'new', '--local', '--docs', 'proj' )
47
+ write( 'notes.md', 'mine' )
48
+ write( File.join( 'unity', 'stale.md' ) )
49
+ write( UNITY_PAGE, 'edited' )
50
+
51
+ ceedling( 'upgrade', 'proj' )
52
+
53
+ expect(File.exist?( docs( 'unity', 'stale.md' ) )).to be( false )
54
+ expect(File.read( docs( UNITY_PAGE ) )).to eq( File.read( File.join( REPO, 'vendor', 'unity', 'docs', 'UnityGettingStartedGuide.md' ) ) )
55
+ expect(File.read( docs( 'notes.md' ) )).to eq( 'mine' )
56
+ end
57
+
58
+ it 'replaces a 1.0.x export with the current layout and keeps the project\'s own files' do
59
+ ceedling( 'new', '--local', 'proj' )
60
+ write( 'CeedlingPacket.md' )
61
+ write( 'Changelog.md' )
62
+ write( File.join( 'plugins', 'gcov.md' ) )
63
+ write( File.join( 'unity', 'stale.md' ) )
64
+ write( 'license.txt' )
65
+ write( 'notes.md', 'mine' )
66
+
67
+ ceedling( 'upgrade', 'proj' )
68
+
69
+ ['CeedlingPacket.md', 'Changelog.md', 'plugins', File.join( 'unity', 'stale.md' )].each do |entry|
70
+ expect(File.exist?( docs( entry ) )).to be( false ), entry
71
+ end
72
+ expect(File.exist?( docs( UNITY_PAGE ) )).to be( true )
73
+ expect(File.read( docs( 'notes.md' ) )).to eq( 'mine' )
74
+ end
75
+
76
+ it 'leaves a docs/ holding only the project\'s own files alone' do
77
+ ceedling( 'new', '--local', 'proj' )
78
+ write( 'notes.md', 'mine' )
79
+
80
+ ceedling( 'upgrade', 'proj' )
81
+
82
+ expect(Dir.children( docs )).to eq( ['notes.md'] )
83
+ end
84
+ end
@@ -8,26 +8,29 @@
8
8
  # ConfiguratorSetup#vendor_frameworks_and_support_files with a real FileWrapper on a real
9
9
  # filesystem. A build brings build/vendor up to date with the gem's frameworks. It writes only
10
10
  # a file that differs from its source, and replaces it whole by renaming a temporary copy over
11
- # it, so a concurrent build never reads a half-written header (GH #1361). A transient copy
12
- # fault, such as a path briefly the wrong type (GH #1292), is retried, and one that never
11
+ # it, so a concurrent build never reads a half-written header (GH #1361). An entry of the
12
+ # wrong type, such as a directory where a file belongs, is replaced (GH #1292, #1376). A
13
+ # transient copy fault, such as a scanner holding a file open, is retried, and one that never
13
14
  # clears names the destination.
14
15
  #
15
16
  # Faults are injected at FileWrapper#replace_file, the one write of each file, so the retry
16
- # loop in FileWrapper#sync_tree is what runs.
17
+ # loop in FileTree is what runs.
17
18
 
18
19
  require 'spec_helper'
19
20
  require 'spec_integration_helper'
21
+ require 'file_tree_helper'
20
22
  require 'ceedling/config/configurator_setup'
21
23
  require 'ceedling/constants'
22
24
 
23
25
  describe 'Vendor copy into build/vendor (integration)' do
24
26
  include IntegrationSpecHelpers
27
+ include FileTreeHelper
25
28
 
26
29
  # A real FileWrapper whose #replace_file raises for files beneath `fault_dir` the first
27
30
  # `raises_left` times. Named apart from other specs' faulting wrappers, since top-level
28
31
  # classes share one RSpec process.
29
32
  class ReplaceFaultingFileWrapper < FileWrapper
30
- def initialize(fault_dir:, raises_left: 1, error: Errno::EISDIR, **kwargs)
33
+ def initialize(fault_dir:, raises_left: 1, error: Errno::EACCES, **kwargs)
31
34
  super(**kwargs)
32
35
  @fault_dir = fault_dir
33
36
  @raises_left = raises_left
@@ -37,7 +40,7 @@ describe 'Vendor copy into build/vendor (integration)' do
37
40
  def replace_file(source, destination)
38
41
  if destination.start_with?( @fault_dir ) && @raises_left > 0
39
42
  @raises_left -= 1
40
- raise @error, "simulated transient type-mismatch on #{destination}"
43
+ raise @error, "simulated fault on #{destination}"
41
44
  end
42
45
  super
43
46
  end
@@ -52,6 +55,7 @@ describe 'Vendor copy into build/vendor (integration)' do
52
55
  ConfiguratorSetup.new(
53
56
  configurator_builder: IntegrationSpecHelpers::NULL,
54
57
  configurator_validator: IntegrationSpecHelpers::NULL,
58
+ configurator_paths_validator: IntegrationSpecHelpers::NULL,
55
59
  loginator: IntegrationSpecHelpers::NULL,
56
60
  reportinator: IntegrationSpecHelpers::NULL,
57
61
  file_wrapper: file_wrapper,
@@ -175,4 +179,69 @@ describe 'Vendor copy into build/vendor (integration)' do
175
179
  }.to raise_error( CeedlingException, /#{Regexp.escape( dest )}/ )
176
180
  end
177
181
  end
182
+ # GH #1376: a deduplicated file the reporter's lstat called a directory left one where any
183
+ # vendored file belongs, not only the framework's own .c file
184
+ it "replaces a directory where any vendored file belongs" do
185
+ with_source_tree({}) do |dir|
186
+ config = flattened_config( dir )
187
+ dest = config[:project_build_vendor_unity_path]
188
+ FileUtils.mkdir_p( File.join( dest, 'unity.h' ) )
189
+ write( File.join( dest, 'internal', 'unity_internals.h', 'junk' ) )
190
+
191
+ vendor( file_wrapper, dir, config )
192
+
193
+ expect( File.read( File.join( dest, 'unity.h' ) ) ).to eq( "// stand-in for unity.h\n" )
194
+ expect( File.file?( File.join( dest, 'internal', 'unity_internals.h' ) ) ).to be(true)
195
+ end
196
+ end
197
+
198
+ it "vendors every file as a file when lstat reports files as directories" do
199
+ with_source_tree({}) do |dir|
200
+ config = flattened_config( dir )
201
+ dest = config[:project_build_vendor_unity_path]
202
+ lie_about_lstat( %r{gem_vendor/} )
203
+
204
+ begin
205
+ vendor( file_wrapper, dir, config )
206
+ ensure
207
+ # The temporary directory is removed by an lstat-based remover
208
+ allow(File).to receive(:lstat).and_call_original
209
+ end
210
+
211
+ expect( File.file?( File.join( dest, 'unity.c' ) ) ).to be(true)
212
+ expect( File.file?( File.join( dest, 'internal', 'unity_internals.h' ) ) ).to be(true)
213
+ end
214
+ end
215
+
216
+ # A read-only gem install would otherwise vendor read-only files, which Windows will not
217
+ # let a later rename replace
218
+ it "vendors owner-writable files from a read-only source, and replaces a read-only destination" do
219
+ with_source_tree({}) do |dir|
220
+ config = flattened_config( dir )
221
+ dest = config[:project_build_vendor_unity_path]
222
+ source = File.join( config[:unity_vendor_path], 'unity', 'src', 'unity.h' )
223
+ File.chmod( 0o444, source )
224
+ vendor( file_wrapper, dir, config )
225
+ expect( File.stat( File.join( dest, 'unity.h' ) ).mode & 0o200 ).to eq( 0o200 )
226
+
227
+ File.chmod( 0o644, source )
228
+ File.write( source, "// a newer unity.h\n" )
229
+ File.chmod( 0o444, File.join( dest, 'unity.h' ) )
230
+ vendor( file_wrapper, dir, config )
231
+
232
+ expect( File.read( File.join( dest, 'unity.h' ) ) ).to eq( "// a newer unity.h\n" )
233
+ end
234
+ end
235
+
236
+ it "fails at once on an error retrying cannot clear" do
237
+ with_source_tree({}) do |dir|
238
+ config = flattened_config( dir )
239
+ dest = config[:project_build_vendor_unity_path]
240
+ faulting = file_wrapper( fault_dir: dest, raises_left: 999, error: Errno::ENOSPC )
241
+ allow(faulting).to receive(:sleep)
242
+
243
+ expect { vendor( faulting, dir, config ) }.to raise_error( Errno::ENOSPC )
244
+ expect(faulting).not_to have_received(:sleep)
245
+ end
246
+ end
178
247
  end
@@ -0,0 +1,88 @@
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 'delegate'
9
+ require 'fileutils'
10
+ require 'tmpdir'
11
+
12
+ # Support for specs that copy real file trees (`filesystem: true`).
13
+ #
14
+ # CEEDLING_TEST_FS_ROOT names a directory on another filesystem, such as a tmpfs, a vfat
15
+ # image, a UNC share, or a VHD volume. These specs then build their trees there instead of
16
+ # the system temp directory, so CI can run one suite on several filesystems.
17
+ module FileTreeHelper
18
+ ROOT_ENV = 'CEEDLING_TEST_FS_ROOT'
19
+
20
+ # A stat result that claims a directory. Reproduces a filesystem whose `lstat` reports a
21
+ # regular file as a directory.
22
+ class LyingStat < SimpleDelegator
23
+ def directory?
24
+ true
25
+ end
26
+
27
+ def file?
28
+ false
29
+ end
30
+
31
+ def symlink?
32
+ false
33
+ end
34
+
35
+ def ftype
36
+ 'directory'
37
+ end
38
+
39
+ def mode
40
+ (__getobj__.mode & ~0o170000) | 0o040000
41
+ end
42
+ end
43
+
44
+ # A temporary directory on the filesystem under test, removed afterwards
45
+ def with_tree_root
46
+ root = ENV[ROOT_ENV].to_s
47
+ dir = root.empty? ? Dir.mktmpdir( 'tree' ) : Dir.mktmpdir( 'tree', root )
48
+ yield File.realpath( dir )
49
+ ensure
50
+ FileUtils.rm_rf( dir ) if dir
51
+ end
52
+
53
+ def write(path, content = File.basename( path ))
54
+ FileUtils.mkdir_p( File.dirname( path ) )
55
+ File.binwrite( path, content )
56
+ end
57
+
58
+ def symlink_or_skip(target, link)
59
+ File.symlink( target, link )
60
+ rescue NotImplementedError, SystemCallError => e
61
+ skip "symlinks are unavailable here (#{e.class})"
62
+ end
63
+
64
+ # Every entry below `dir`, dotfiles included, as sorted relative paths
65
+ def tree_of(dir)
66
+ Dir.glob( '**/*', File::FNM_DOTMATCH, base: dir ).reject { |path| path == '.' || path.end_with?( '/.' ) }.sort
67
+ end
68
+
69
+ # Makes `File.lstat` lie, claiming a directory, for each path matching `pattern`
70
+ def lie_about_lstat(pattern)
71
+ allow(File).to receive(:lstat).and_wrap_original do |original, path, *rest|
72
+ status = original.call( path, *rest )
73
+ path.to_s.match?( pattern ) ? LyingStat.new( status ) : status
74
+ end
75
+ end
76
+
77
+ # Fails any `File.lstat` call, proving code under test never classifies by it
78
+ def forbid_lstat
79
+ allow(File).to receive(:lstat).and_raise( RuntimeError, 'File.lstat must not be called' )
80
+ end
81
+
82
+ # The publish step FileWrapper#replace_file performs: a whole copy renamed into place
83
+ PUBLISH = lambda do |source, destination|
84
+ temp = "#{destination}.publishing"
85
+ FileUtils.cp( source, temp )
86
+ File.rename( temp, destination )
87
+ end
88
+ end