ceedling 1.2.0 → 1.2.1

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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/README.md +47 -50
  4. data/Rakefile +3 -2
  5. data/bin/app_cfg.rb +21 -0
  6. data/bin/cli_handler.rb +7 -1
  7. data/bin/cli_helper.rb +5 -0
  8. data/docs/Changelog.md +10 -0
  9. data/docs/overrides/main.html +11 -8
  10. data/lib/ceedling/config/configurator.rb +5 -2
  11. data/lib/ceedling/config/configurator_setup.rb +26 -15
  12. data/lib/ceedling/file_wrapper.rb +56 -26
  13. data/lib/ceedling/rake_app/test_task_name.rb +29 -0
  14. data/lib/ceedling/rakefile.rb +1 -0
  15. data/lib/ceedling/rakefiles/tests/rules_tests.rake +3 -20
  16. data/lib/ceedling/rakefiles/tests/rules_tests_generate.rake +4 -30
  17. data/lib/ceedling/setupinator.rb +4 -0
  18. data/lib/ceedling/test_invoker/test_build_executor.rb +18 -1
  19. data/lib/version.rb +1 -1
  20. data/plugins/bullseye/bullseye.rake +6 -9
  21. data/plugins/gcov/gcov.rake +6 -9
  22. data/plugins/sanitize/sanitize.rake +6 -6
  23. data/plugins/valgrind/valgrind.rake +6 -6
  24. data/site-local/404.html +6 -20
  25. data/site-local/assets/javascripts/{bundle.d7400e89.min.js → bundle.c21346df.min.js} +3 -3
  26. data/site-local/assets/javascripts/{bundle.d7400e89.min.js.map → bundle.c21346df.min.js.map} +2 -2
  27. data/site-local/assets/javascripts/workers/{search.2c215733.min.js → search.a7633da7.min.js} +2 -2
  28. data/site-local/assets/javascripts/workers/{search.2c215733.min.js.map → search.a7633da7.min.js.map} +1 -1
  29. data/site-local/assets/stylesheets/main.57a0d79d.min.css +6 -0
  30. data/site-local/assets/stylesheets/main.57a0d79d.min.css.map +1 -0
  31. data/site-local/assets/stylesheets/palette.fb2fffe2.min.css +4 -0
  32. data/site-local/assets/stylesheets/palette.fb2fffe2.min.css.map +1 -0
  33. data/site-local/configuration/environment-vars.html +6 -20
  34. data/site-local/configuration/global-collections.html +6 -20
  35. data/site-local/configuration/index.html +6 -20
  36. data/site-local/configuration/loading.html +6 -20
  37. data/site-local/configuration/mixins.html +10 -24
  38. data/site-local/configuration/parallel-builds.html +6 -20
  39. data/site-local/configuration/project-file.html +8 -22
  40. data/site-local/configuration/reference/cexception.html +6 -20
  41. data/site-local/configuration/reference/cmock.html +6 -20
  42. data/site-local/configuration/reference/defines.html +16 -30
  43. data/site-local/configuration/reference/environment.html +10 -24
  44. data/site-local/configuration/reference/extension.html +6 -20
  45. data/site-local/configuration/reference/files.html +13 -27
  46. data/site-local/configuration/reference/flags.html +13 -27
  47. data/site-local/configuration/reference/index.html +6 -20
  48. data/site-local/configuration/reference/libraries.html +6 -20
  49. data/site-local/configuration/reference/mixins.html +6 -20
  50. data/site-local/configuration/reference/partials.html +6 -20
  51. data/site-local/configuration/reference/paths.html +11 -25
  52. data/site-local/configuration/reference/plugins.html +6 -20
  53. data/site-local/configuration/reference/project.html +16 -30
  54. data/site-local/configuration/reference/release-build.html +6 -20
  55. data/site-local/configuration/reference/test-build.html +6 -20
  56. data/site-local/configuration/reference/test-runner.html +6 -20
  57. data/site-local/configuration/reference/tools.html +18 -32
  58. data/site-local/configuration/reference/unity.html +6 -20
  59. data/site-local/configuration/which-ceedling.html +6 -20
  60. data/site-local/development/index.html +6 -20
  61. data/site-local/development/plugins/configuration.html +6 -20
  62. data/site-local/development/plugins/index.html +6 -20
  63. data/site-local/development/plugins/plugin-subclass.html +54 -68
  64. data/site-local/development/plugins/rake-tasks.html +8 -22
  65. data/site-local/development/releases.html +79 -28
  66. data/site-local/development/workflow.html +14 -28
  67. data/site-local/getting-started/builds.html +6 -20
  68. data/site-local/getting-started/command-line.html +6 -20
  69. data/site-local/getting-started/example-projects/cipher-quest.html +6 -20
  70. data/site-local/getting-started/example-projects/index.html +6 -20
  71. data/site-local/getting-started/example-projects/temp-sensor.html +6 -20
  72. data/site-local/getting-started/example-projects/wondrous-forest.html +6 -20
  73. data/site-local/getting-started/index.html +6 -20
  74. data/site-local/getting-started/installation.html +6 -20
  75. data/site-local/getting-started/quick-start.html +6 -20
  76. data/site-local/help.html +6 -20
  77. data/site-local/index.html +11 -24
  78. data/site-local/overview/build-system.html +6 -20
  79. data/site-local/overview/index.html +6 -20
  80. data/site-local/overview/test-environments.html +6 -20
  81. data/site-local/overview/tools-and-frameworks.html +6 -20
  82. data/site-local/plugins/beep.html +6 -20
  83. data/site-local/plugins/bullseye/index.html +13 -24
  84. data/site-local/plugins/bullseye/licensing.html +14 -28
  85. data/site-local/plugins/command-hooks.html +8 -22
  86. data/site-local/plugins/compile-commands-json-db.html +6 -20
  87. data/site-local/plugins/cppcheck.html +8 -22
  88. data/site-local/plugins/dependencies.html +10 -24
  89. data/site-local/plugins/fff.html +7 -21
  90. data/site-local/plugins/gcov/examples.html +7 -21
  91. data/site-local/plugins/gcov/gcovr.html +7 -21
  92. data/site-local/plugins/gcov/index.html +6 -20
  93. data/site-local/plugins/gcov/overview.html +9 -20
  94. data/site-local/plugins/gcov/reportgenerator.html +9 -23
  95. data/site-local/plugins/gcov/reporting.html +6 -20
  96. data/site-local/plugins/gcov/setup.html +6 -20
  97. data/site-local/plugins/gcov/tool-versions.html +6 -20
  98. data/site-local/plugins/gcov/troubleshooting.html +10 -24
  99. data/site-local/plugins/index.html +6 -20
  100. data/site-local/plugins/module-generator.html +21 -35
  101. data/site-local/plugins/report-build-warnings-log.html +53 -67
  102. data/site-local/plugins/report-tests-gtestlike-stdout.html +6 -20
  103. data/site-local/plugins/report-tests-ide-stdout.html +43 -57
  104. data/site-local/plugins/report-tests-log-factory.html +50 -64
  105. data/site-local/plugins/report-tests-pretty-stdout.html +7 -21
  106. data/site-local/plugins/report-tests-raw-output-log.html +6 -20
  107. data/site-local/plugins/report-tests-teamcity-stdout.html +16 -30
  108. data/site-local/plugins/sanitize.html +54 -64
  109. data/site-local/plugins/valgrind.html +46 -56
  110. data/site-local/project/index.html +6 -20
  111. data/site-local/project/release-history.html +6 -20
  112. data/site-local/project/sbom.html +6 -20
  113. data/site-local/project/upgrade.html +6 -20
  114. data/site-local/reference/build-directives.html +6 -20
  115. data/site-local/reference/command-line.html +6 -20
  116. data/site-local/reference/environment-vars.html +6 -20
  117. data/site-local/reference/gcov-plugin.html +6 -20
  118. data/site-local/reference/global-collections.html +6 -20
  119. data/site-local/reference/index.html +6 -20
  120. data/site-local/reference/partials-macros.html +6 -20
  121. data/site-local/reference/project-configuration.html +6 -20
  122. data/site-local/sitemap.xml.gz +0 -0
  123. data/site-local/snapshot/examples/cipher_quest/index.html +16 -30
  124. data/site-local/snapshot/examples/temp_sensor/index.html +8 -22
  125. data/site-local/snapshot/examples/wondrous_forest/index.html +6 -20
  126. data/site-local/testing-guide/build-directives.html +14 -28
  127. data/site-local/testing-guide/conventions.html +13 -27
  128. data/site-local/testing-guide/frameworks.html +11 -25
  129. data/site-local/testing-guide/index.html +6 -20
  130. data/site-local/testing-guide/partials/configuration.html +8 -22
  131. data/site-local/testing-guide/partials/conventions.html +8 -22
  132. data/site-local/testing-guide/partials/directives.html +16 -30
  133. data/site-local/testing-guide/partials/example.html +14 -28
  134. data/site-local/testing-guide/partials/index.html +6 -20
  135. data/site-local/testing-guide/partials/overview.html +6 -20
  136. data/site-local/testing-guide/partials/variables.html +11 -25
  137. data/site-local/testing-guide/test-cases.html +13 -27
  138. data/site-local/testing-guide/test-sample.html +12 -26
  139. data/site-local/testing-guide/test-suite-anatomy.html +6 -20
  140. data/spec/integration/configurator_pipeline_spec.rb +10 -0
  141. data/spec/integration/dependencies/dependency_tracker_core_spec.rb +40 -0
  142. data/spec/integration/rake_task_registry_plugins_spec.rb +95 -0
  143. data/spec/integration/vendor_copy_transient_io_retry_spec.rb +122 -74
  144. data/spec/support/integration/configurator_integration_helper.rb +4 -1
  145. data/spec/units/bin/app_cfg_spec.rb +10 -0
  146. data/spec/units/bin/cli_handler_spec.rb +2 -1
  147. data/spec/units/bin/cli_helper_spec.rb +10 -0
  148. data/spec/units/config/configurator_setup_spec.rb +61 -43
  149. data/spec/units/config/configurator_spec.rb +10 -1
  150. data/spec/units/file_wrapper_spec.rb +66 -27
  151. data/spec/units/rake_app/test_task_name_spec.rb +47 -0
  152. data/spec/units/test_build_executor_spec.rb +42 -2
  153. metadata +16 -11
  154. data/site-local/assets/stylesheets/main.ec1eaa64.min.css +0 -1
  155. data/site-local/assets/stylesheets/main.ec1eaa64.min.css.map +0 -1
  156. data/site-local/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
  157. data/site-local/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
@@ -5,44 +5,37 @@
5
5
  # SPDX-License-Identifier: MIT
6
6
  # =========================================================================
7
7
 
8
- # Integration coverage for Issue #1292: a vendor destination (build/vendor/unity/src,
9
- # etc.) occasionally turns up as the wrong file/directory type mid-copy, crashing with
10
- # Errno::ENOTDIR/EISDIR. Does FileWrapper#cp_r_with_retry actually let
11
- # ConfiguratorSetup#vendor_frameworks_and_support_files ride out a transient fault of
12
- # that shape, end to end against a real filesystem -- and does a fault that never
13
- # clears still surface as a clear, path-naming CeedlingException rather than a raw Errno?
8
+ # ConfiguratorSetup#vendor_frameworks_and_support_files with a real FileWrapper on a real
9
+ # filesystem. A build brings build/vendor up to date with the gem's frameworks. It writes only
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
13
+ # clears names the destination.
14
14
  #
15
- # Modeled directly on spec/integration/preprocessing_transient_io_retry_spec.rb: a real
16
- # ConfiguratorSetup + real FileWrapper subclass that controls exactly one #cp_r call
17
- # (matched by destination path) so the fault is deterministic. Faulting at #cp_r rather
18
- # than #cp_r_with_retry itself matters here -- cp_r_with_retry's own retry loop (already
19
- # proven exhaustively in file_wrapper_spec.rb) is exactly the mechanism this spec needs
20
- # to actually run.
15
+ # 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.
21
17
 
22
18
  require 'spec_helper'
23
19
  require 'spec_integration_helper'
24
20
  require 'ceedling/config/configurator_setup'
25
21
  require 'ceedling/constants'
26
22
 
27
- describe 'Vendor-copy transient I/O retry (integration)' do
23
+ describe 'Vendor copy into build/vendor (integration)' do
28
24
  include IntegrationSpecHelpers
29
25
 
30
- # A real FileWrapper whose #cp_r raises for `fault_dest` the first `raises_left`
31
- # times it's called, then behaves normally -- every other destination is untouched.
32
- # Real FileWrapper#cp_r_with_retry (inherited, not overridden) is what actually retries.
33
- # Named distinctly from preprocessing_transient_io_retry_spec.rb's own FaultingFileWrapper
34
- # -- both are top-level classes loaded into the same RSpec process, and Ruby's open
35
- # classes would otherwise let the second file's definition silently clobber the first's.
36
- class CpRFaultingFileWrapper < FileWrapper
37
- def initialize(fault_dest:, raises_left: 1, error: Errno::EISDIR, **kwargs)
26
+ # A real FileWrapper whose #replace_file raises for files beneath `fault_dir` the first
27
+ # `raises_left` times. Named apart from other specs' faulting wrappers, since top-level
28
+ # classes share one RSpec process.
29
+ class ReplaceFaultingFileWrapper < FileWrapper
30
+ def initialize(fault_dir:, raises_left: 1, error: Errno::EISDIR, **kwargs)
38
31
  super(**kwargs)
39
- @fault_dest = fault_dest
32
+ @fault_dir = fault_dir
40
33
  @raises_left = raises_left
41
34
  @error = error
42
35
  end
43
36
 
44
- def cp_r(source, destination, options={})
45
- if destination == @fault_dest && @raises_left > 0
37
+ def replace_file(source, destination)
38
+ if destination.start_with?( @fault_dir ) && @raises_left > 0
46
39
  @raises_left -= 1
47
40
  raise @error, "simulated transient type-mismatch on #{destination}"
48
41
  end
@@ -50,81 +43,136 @@ describe 'Vendor-copy transient I/O retry (integration)' do
50
43
  end
51
44
  end
52
45
 
53
- # Real ConfiguratorSetup wired to `file_wrapper`; every other collaborator is
54
- # untouched by #vendor_frameworks_and_support_files, so NULL stands in for them.
46
+ def file_wrapper(**fault)
47
+ options = { loginator: IntegrationSpecHelpers::NULL, verbosinator: IntegrationSpecHelpers::NULL }
48
+ return fault.empty? ? FileWrapper.new( options ) : ReplaceFaultingFileWrapper.new( **fault, **options )
49
+ end
50
+
55
51
  def build_configurator_setup(file_wrapper)
56
52
  ConfiguratorSetup.new(
57
53
  configurator_builder: IntegrationSpecHelpers::NULL,
58
54
  configurator_validator: IntegrationSpecHelpers::NULL,
59
- loginator: IntegrationSpecHelpers::NULL,
60
- reportinator: IntegrationSpecHelpers::NULL,
61
- file_wrapper: file_wrapper,
62
- system_wrapper: IntegrationSpecHelpers::NULL,
63
- tool_executor: IntegrationSpecHelpers::NULL
55
+ loginator: IntegrationSpecHelpers::NULL,
56
+ reportinator: IntegrationSpecHelpers::NULL,
57
+ file_wrapper: file_wrapper,
58
+ system_wrapper: IntegrationSpecHelpers::NULL,
59
+ tool_executor: IntegrationSpecHelpers::NULL
64
60
  )
65
61
  end
66
62
 
67
- # A minimal but real fixture standing in for the gem's own vendor/unity/src -- one
68
- # real file, so a real FileUtils.cp_r has real directory-tree content to walk.
63
+ # A stand-in for the gem's vendor/unity/src, with a nested file to show subdirectories copy
69
64
  def flattened_config(dir)
70
- unity_src = File.join(dir, 'gem_vendor', 'unity', 'src')
71
- FileUtils.mkdir_p(unity_src)
72
- File.write(File.join(unity_src, 'unity.c'), "// stand-in for the real unity.c\n")
65
+ unity_src = File.join( dir, 'gem_vendor', 'unity', 'src' )
66
+ FileUtils.mkdir_p( File.join( unity_src, 'internal' ) )
67
+ File.write( File.join( unity_src, 'unity.c' ), "// stand-in for unity.c\n" )
68
+ File.write( File.join( unity_src, 'unity.h' ), "// stand-in for unity.h\n" )
69
+ File.write( File.join( unity_src, 'internal', 'unity_internals.h' ), "// stand-in\n" )
73
70
 
74
71
  {
75
- # UNITY_LIB_PATH ('unity/src') is joined onto this root -- it must be the
76
- # vendor root, not 'unity' itself, matching CEEDLING_VENDOR's real shape.
77
- unity_vendor_path: File.join(dir, 'gem_vendor'),
78
- project_build_vendor_unity_path: File.join(dir, 'build', 'vendor', 'unity', 'src'),
79
- project_use_mocks: false,
80
- project_use_exceptions: false,
81
- project_use_backtrace: false,
82
- project_use_partials: false,
72
+ # UNITY_LIB_PATH ('unity/src') joins this vendor root
73
+ unity_vendor_path: File.join( dir, 'gem_vendor' ),
74
+ project_build_vendor_unity_path: File.join( dir, 'build', 'vendor', 'unity', 'src' ),
75
+ project_use_mocks: false,
76
+ project_use_exceptions: false,
77
+ project_use_backtrace: :none,
78
+ project_use_partials: false,
83
79
  }
84
80
  end
85
81
 
86
- it "recovers from a transient copy failure on its own vendor destination instead of crashing the build" do
82
+ def vendor(file_wrapper, dir, config)
83
+ build_configurator_setup( file_wrapper ).vendor_frameworks_and_support_files( dir, config, test_build: true, release_build: false )
84
+ end
85
+
86
+ def snapshot(dest)
87
+ return Dir.glob( File.join( dest, '**', '*' ) ).select { |path| File.file?( path ) }.to_h do |path|
88
+ stat = File.stat( path )
89
+ [path, [stat.ino, stat.mtime]]
90
+ end
91
+ end
92
+
93
+ def leftovers(dest)
94
+ return Dir.glob( File.join( dest, '**', '*' ), File::FNM_DOTMATCH ).select { |path| File.basename( path ).include?( '.tmp' ) }
95
+ end
96
+
97
+ it "copies every file, including those in subdirectories, into a fresh destination" do
87
98
  with_source_tree({}) do |dir|
88
- config = flattened_config(dir)
99
+ config = flattened_config( dir )
89
100
  dest = config[:project_build_vendor_unity_path]
90
- # Real usage always runs build_directory_structure (which mkdir_p's every
91
- # vendor destination) before this copy -- match that precondition here so the
92
- # only fault in play is the one this spec injects, not a missing parent dir.
93
- FileUtils.mkdir_p(dest)
94
-
95
- # Exactly one raise, well within cp_r_with_retry's own retry budget -- the
96
- # source is otherwise 100% fine, the real copy proceeds either way.
97
- faulting_fw = CpRFaultingFileWrapper.new(
98
- fault_dest: dest, raises_left: 1,
99
- loginator: IntegrationSpecHelpers::NULL, verbosinator: IntegrationSpecHelpers::NULL
100
- )
101
- setup = build_configurator_setup(faulting_fw)
102
101
 
103
- expect {
104
- setup.vendor_frameworks_and_support_files(dir, config)
105
- }.not_to raise_error
102
+ vendor( file_wrapper, dir, config )
106
103
 
107
- expect(File.exist?(File.join(dest, 'unity.c'))).to be true
104
+ expect( File.read( File.join( dest, 'unity.h' ) ) ).to eq( "// stand-in for unity.h\n" )
105
+ expect( File.exist?( File.join( dest, 'internal', 'unity_internals.h' ) ) ).to be(true)
106
+ expect( leftovers( dest ) ).to be_empty
108
107
  end
109
108
  end
110
109
 
111
- it "still raises a clear, path-naming CeedlingException when the fault never clears" do
110
+ it "rewrites nothing when the destination already matches" do
112
111
  with_source_tree({}) do |dir|
113
- config = flattened_config(dir)
112
+ config = flattened_config( dir )
114
113
  dest = config[:project_build_vendor_unity_path]
115
- FileUtils.mkdir_p(dest)
114
+ vendor( file_wrapper, dir, config )
115
+ before = snapshot( dest )
116
+ sleep( 1.1 ) # Past the coarsest file modification time resolution
116
117
 
117
- # More raises than cp_r_with_retry's own retry budget (initial attempt + 4
118
- # retries) -- every attempt fails, so the fault genuinely never clears.
119
- permanently_faulting_fw = CpRFaultingFileWrapper.new(
120
- fault_dest: dest, raises_left: 999,
121
- loginator: IntegrationSpecHelpers::NULL, verbosinator: IntegrationSpecHelpers::NULL
122
- )
123
- setup = build_configurator_setup(permanently_faulting_fw)
118
+ vendor( file_wrapper, dir, config )
119
+
120
+ expect( snapshot( dest ) ).to eq( before )
121
+ end
122
+ end
123
+
124
+ it "restores an edited file by replacing it whole, and leaves matching files alone" do
125
+ with_source_tree({}) do |dir|
126
+ config = flattened_config( dir )
127
+ dest = config[:project_build_vendor_unity_path]
128
+ vendor( file_wrapper, dir, config )
129
+ edited = File.join( dest, 'unity.h' )
130
+ File.write( edited, "// an errant edit\n" )
131
+ before = snapshot( dest )
132
+
133
+ vendor( file_wrapper, dir, config )
134
+
135
+ expect( File.read( edited ) ).to eq( "// stand-in for unity.h\n" )
136
+ expect( File.stat( edited ).ino ).not_to eq( before[edited].first ) unless SystemWrapper.windows?
137
+ untouched = File.join( dest, 'unity.c' )
138
+ expect( snapshot( dest )[untouched] ).to eq( before[untouched] )
139
+ expect( leftovers( dest ) ).to be_empty
140
+ end
141
+ end
142
+
143
+ it "restores a missing file" do
144
+ with_source_tree({}) do |dir|
145
+ config = flattened_config( dir )
146
+ dest = config[:project_build_vendor_unity_path]
147
+ vendor( file_wrapper, dir, config )
148
+ FileUtils.rm( File.join( dest, 'internal', 'unity_internals.h' ) )
149
+
150
+ vendor( file_wrapper, dir, config )
151
+
152
+ expect( File.exist?( File.join( dest, 'internal', 'unity_internals.h' ) ) ).to be(true)
153
+ end
154
+ end
155
+
156
+ it "recovers from a transient copy failure instead of crashing the build" do
157
+ with_source_tree({}) do |dir|
158
+ config = flattened_config( dir )
159
+ dest = config[:project_build_vendor_unity_path]
160
+
161
+ expect { vendor( file_wrapper( fault_dir: dest, raises_left: 1 ), dir, config ) }.not_to raise_error
162
+
163
+ expect( File.exist?( File.join( dest, 'unity.c' ) ) ).to be(true)
164
+ expect( leftovers( dest ) ).to be_empty
165
+ end
166
+ end
167
+
168
+ it "raises a clear, path-naming CeedlingException when the fault never clears" do
169
+ with_source_tree({}) do |dir|
170
+ config = flattened_config( dir )
171
+ dest = config[:project_build_vendor_unity_path]
124
172
 
125
173
  expect {
126
- setup.vendor_frameworks_and_support_files(dir, config)
127
- }.to raise_error(CeedlingException, /#{Regexp.escape(dest)}/)
174
+ vendor( file_wrapper( fault_dir: dest, raises_left: 999 ), dir, config )
175
+ }.to raise_error( CeedlingException, /#{Regexp.escape( dest )}/ )
128
176
  end
129
177
  end
130
178
  end
@@ -100,7 +100,8 @@ module ConfiguratorIntegrationHelpers
100
100
  end
101
101
 
102
102
  # Processes `yaml` as a build's setup would and returns the object graph
103
- def configure(yaml, include_test_case: '', exclude_test_case: '', plugins_path: File.join( CEEDLING_ROOT_PATH, 'plugins' ))
103
+ # `test_build:` stands for the requested tasks building tests, which populates build/vendor
104
+ def configure(yaml, include_test_case: '', exclude_test_case: '', test_build: true, plugins_path: File.join( CEEDLING_ROOT_PATH, 'plugins' ))
104
105
  objects = configurator_objects( quiet_loginator )
105
106
  objects[:configurator_builder].constants_namespace = constants
106
107
 
@@ -121,6 +122,8 @@ module ConfiguratorIntegrationHelpers
121
122
  include_test_case: include_test_case,
122
123
  exclude_test_case: exclude_test_case,
123
124
  force_test_rerun: false,
125
+ test_build_tasks?: test_build,
126
+ release_build_tasks?: false,
124
127
  log_filepath: nil,
125
128
  ceedling_plugins_path: plugins_path,
126
129
  ceedling_lib_path: File.join( CEEDLING_ROOT_PATH, 'lib', 'ceedling' ),
@@ -81,6 +81,16 @@ describe CeedlingAppConfig do
81
81
  expect(@app_cfg.build_tasks?).to eq( true )
82
82
  end
83
83
 
84
+ it '#set_test_build_tasks and #set_release_build_tasks update their flags, which default to false' do
85
+ expect(@app_cfg.test_build_tasks?).to eq( false )
86
+ expect(@app_cfg.release_build_tasks?).to eq( false )
87
+
88
+ @app_cfg.set_test_build_tasks( true )
89
+ @app_cfg.set_release_build_tasks( true )
90
+ expect(@app_cfg.test_build_tasks?).to eq( true )
91
+ expect(@app_cfg.release_build_tasks?).to eq( true )
92
+ end
93
+
84
94
  it '#set_tests_graceful_fail updates :tests_graceful_fail? and #tests_graceful_fail?' do
85
95
  @app_cfg.set_tests_graceful_fail( true )
86
96
  expect(@app_cfg[:tests_graceful_fail?]).to eq( true )
@@ -109,7 +109,8 @@ describe CliHandler do
109
109
  )
110
110
 
111
111
  expect(@loginator).to receive(:console) do |message, _label|
112
- expect(message).to include( 'Ceedling Launcher => 1.2.0' )
112
+ # The launcher reports the version of the Ceedling running this spec
113
+ expect(message).to include( "Ceedling Launcher => #{Ceedling::Version::GEM}" )
113
114
  expect(message).to include( 'Ceedling App => 1.2.0' )
114
115
  end
115
116
 
@@ -111,6 +111,16 @@ describe CliHelper do
111
111
  end
112
112
  end
113
113
 
114
+ # Whether a command's tasks build a release decides what build/vendor needs (GH #1361)
115
+ describe '#release_task?' do
116
+ it 'is true when any task belongs to a release-building namespace' do
117
+ allow(@rake_task_registry).to receive(:task_is?) { |task, tag| task == 'release' && tag == RakeTaskRegistry::TAG_RELEASE }
118
+
119
+ expect( @cli_helper.release_task?( tasks: ['test:all', 'release'] ) ).to be(true)
120
+ expect( @cli_helper.release_task?( tasks: ['test:all', 'files:test'] ) ).to be(false)
121
+ end
122
+ end
123
+
114
124
  describe '#which_ceedling?' do
115
125
  before(:each) do
116
126
  @app_cfg = CeedlingAppConfig.new
@@ -225,74 +225,93 @@ describe ConfiguratorSetup do
225
225
  end
226
226
  end
227
227
 
228
+ # Only a build populates build/vendor, so a command that only reads the project, such as
229
+ # files:test or dumpconfig, leaves a concurrent build's files alone (GH #1361)
228
230
  context "#vendor_frameworks_and_support_files" do
229
231
  def flattened_config
230
232
  {
231
- unity_vendor_path: '/gem/vendor/unity',
233
+ unity_vendor_path: '/gem/vendor',
232
234
  project_build_vendor_unity_path: '/build/vendor/unity/src',
233
- cmock_vendor_path: '/gem/vendor/cmock',
235
+ cmock_vendor_path: '/gem/vendor',
234
236
  project_build_vendor_cmock_path: '/build/vendor/cmock/src',
235
- cexception_vendor_path: '/gem/vendor/c_exception',
237
+ cexception_vendor_path: '/gem/vendor',
236
238
  project_build_vendor_cexception_path: '/build/vendor/c_exception/lib',
239
+ project_build_vendor_ceedling_path: '/build/vendor/ceedling',
240
+ project_build_tests_root: '/build/test',
237
241
  project_use_mocks: true,
238
242
  project_use_exceptions: true,
239
- project_use_backtrace: false,
240
- project_use_partials: false,
243
+ project_use_backtrace: :gdb,
244
+ project_use_partials: true,
241
245
  }
242
246
  end
243
247
 
244
248
  before(:each) do
245
249
  allow(@file_wrapper).to receive(:exist?).and_return(false)
246
250
  allow(@file_wrapper).to receive(:rm_rf)
247
- allow(@file_wrapper).to receive(:cp_r_with_retry)
251
+ allow(@file_wrapper).to receive(:mkdir)
252
+ allow(@file_wrapper).to receive(:sync_tree)
253
+ allow(@file_wrapper).to receive(:sync_file)
248
254
  end
249
255
 
250
- it "copies the gdb backtrace script and the Partials header when those features are in use" do
251
- allow(@file_wrapper).to receive(:mkdir)
252
- allow(@file_wrapper).to receive(:cp_r)
253
- config = flattened_config.merge(
254
- project_use_backtrace: :gdb, project_build_tests_root: '/build/test',
255
- project_use_partials: true, project_build_vendor_ceedling_path: '/build/vendor/ceedling'
256
- )
256
+ def vendor(config = flattened_config, test_build: false, release_build: false)
257
+ @setup.vendor_frameworks_and_support_files( '/gem/lib', config, test_build: test_build, release_build: release_build )
258
+ end
257
259
 
258
- @setup.vendor_frameworks_and_support_files('/gem/lib', config)
260
+ def synced_trees
261
+ synced = []
262
+ allow(@file_wrapper).to receive(:sync_tree) { |source, destination| synced << [source, destination] }
263
+ yield
264
+ return synced
265
+ end
266
+
267
+ it "brings Unity, CMock, CException, the gdb backtrace script and the Partials header up to date for a test build" do
268
+ synced = synced_trees { vendor( test_build: true ) }
259
269
 
270
+ expect(synced).to eq( [
271
+ ['/gem/vendor/unity/src', '/build/vendor/unity/src'],
272
+ ['/gem/vendor/cmock/src', '/build/vendor/cmock/src'],
273
+ ['/gem/vendor/c_exception/lib', '/build/vendor/c_exception/lib']
274
+ ] )
260
275
  expect(@file_wrapper).to have_received(:mkdir).with('/build/test')
261
- expect(@file_wrapper).to have_received(:cp_r).with(File.join('/gem/lib', BACKTRACE_GDB_SCRIPT_FILE), '/build/test')
262
- expect(@file_wrapper).to have_received(:cp_r).with('/gem/lib/ceedling.h', '/build/vendor/ceedling')
276
+ expect(@file_wrapper).to have_received(:sync_file).with(File.join('/gem/lib', BACKTRACE_GDB_SCRIPT_FILE), '/build/test')
277
+ expect(@file_wrapper).to have_received(:sync_file).with('/gem/lib/ceedling.h', '/build/vendor/ceedling')
263
278
  end
264
279
 
265
- it "always copies Unity, CMock, and CException, regardless of whether the destination looks already populated" do
266
- # A prior run's destination "looking populated and uncorrupted" is exactly the
267
- # state a skip-check would trust -- simulate it directly (dest is a real
268
- # directory, its marker file is a real file, nothing corrupted) to prove there
269
- # is no such skip.
270
- allow(@file_wrapper).to receive(:exist?).and_return(true)
271
- allow(@file_wrapper).to receive(:directory?) do |path|
272
- !path.end_with?('.c') # marker files (unity.c, cmock.c, CException.c) are plain files; their parent dirs are directories
273
- end
280
+ it "leaves out CMock, CException, the backtrace script and the Partials header when a test build doesn't use them" do
281
+ config = flattened_config.merge( project_use_mocks: false, project_use_exceptions: false, project_use_backtrace: :simple, project_use_partials: false )
274
282
 
275
- @setup.vendor_frameworks_and_support_files('/gem/lib', flattened_config)
283
+ synced = synced_trees { vendor( config, test_build: true ) }
276
284
 
277
- expect(@file_wrapper).to have_received(:cp_r_with_retry)
278
- .with(a_string_including('/gem/vendor/unity'), '/build/vendor/unity/src')
279
- expect(@file_wrapper).to have_received(:cp_r_with_retry)
280
- .with(a_string_including('/gem/vendor/cmock'), '/build/vendor/cmock/src')
281
- expect(@file_wrapper).to have_received(:cp_r_with_retry)
282
- .with(a_string_including('/gem/vendor/c_exception'), '/build/vendor/c_exception/lib')
285
+ expect(synced).to eq( [['/gem/vendor/unity/src', '/build/vendor/unity/src']] )
286
+ expect(@file_wrapper).to_not have_received(:sync_file)
283
287
  end
284
288
 
285
- it "skips CMock and CException copying when the project doesn't use them" do
286
- config = flattened_config.merge(project_use_mocks: false, project_use_exceptions: false)
289
+ it "brings only CException up to date for a release build" do
290
+ synced = synced_trees { vendor( release_build: true ) }
291
+
292
+ expect(synced).to eq( [['/gem/vendor/c_exception/lib', '/build/vendor/c_exception/lib']] )
293
+ expect(@file_wrapper).to_not have_received(:sync_file)
294
+ end
295
+
296
+ it "copies nothing for a release build without exceptions" do
297
+ vendor( flattened_config.merge( project_use_exceptions: false ), release_build: true )
298
+
299
+ expect(@file_wrapper).to_not have_received(:sync_tree)
300
+ expect(@file_wrapper).to_not have_received(:sync_file)
301
+ end
302
+
303
+ it "copies each framework once when a command builds both tests and a release" do
304
+ synced = synced_trees { vendor( test_build: true, release_build: true ) }
305
+
306
+ expect(synced.map( &:last )).to eq( ['/build/vendor/unity/src', '/build/vendor/cmock/src', '/build/vendor/c_exception/lib'] )
307
+ end
287
308
 
288
- @setup.vendor_frameworks_and_support_files('/gem/lib', config)
309
+ it "touches nothing beneath build/vendor for a command that builds nothing" do
310
+ vendor
289
311
 
290
- expect(@file_wrapper).to have_received(:cp_r_with_retry)
291
- .with(a_string_including('/gem/vendor/unity'), '/build/vendor/unity/src')
292
- expect(@file_wrapper).to_not have_received(:cp_r_with_retry)
293
- .with(a_string_including('/gem/vendor/cmock'), anything)
294
- expect(@file_wrapper).to_not have_received(:cp_r_with_retry)
295
- .with(a_string_including('/gem/vendor/c_exception'), anything)
312
+ expect(@file_wrapper).to_not have_received(:sync_tree)
313
+ expect(@file_wrapper).to_not have_received(:sync_file)
314
+ expect(@file_wrapper).to_not have_received(:rm_rf)
296
315
  end
297
316
 
298
317
  it "heals a corrupted (wrong-typed) vendor destination before copying" do
@@ -300,13 +319,12 @@ describe ConfiguratorSetup do
300
319
  allow(@file_wrapper).to receive(:directory?).with('/build/vendor/unity/src').and_return(false)
301
320
  allow(@loginator).to receive(:log)
302
321
 
303
- @setup.vendor_frameworks_and_support_files('/gem/lib', flattened_config)
322
+ vendor( test_build: true )
304
323
 
305
324
  expect(@file_wrapper).to have_received(:rm_rf).with('/build/vendor/unity/src')
306
325
  end
307
326
  end
308
327
 
309
- # The validations below log each problem they find and report overall validity
310
328
  context "configuration validation" do
311
329
  before(:each) do
312
330
  allow(@loginator).to receive(:log)
@@ -902,13 +902,22 @@ describe Configurator do
902
902
  flattened = { flat_a: 1, flat_environment: [ { cc: 'gcc' } ] }
903
903
  expect( @configurator_setup ).to have_received(:build_project_config).with( 'lib', 'logs', flattened ).ordered
904
904
  expect( @configurator_setup ).to have_received(:build_directory_structure).with( flattened ).ordered
905
- expect( @configurator_setup ).to have_received(:vendor_frameworks_and_support_files).with( 'lib', flattened ).ordered
905
+ expect( @configurator_setup ).to have_received(:vendor_frameworks_and_support_files).with( 'lib', flattened, test_build: false, release_build: false ).ordered
906
906
  expect( @configurator_setup ).to have_received(:build_project_collections).with( flattened ).ordered
907
907
  expect( @configurator_setup ).to have_received(:build_constants_and_accessors).with( flattened, anything ).ordered
908
908
  expect( @configurator_setup ).to have_received(:build_constants_and_accessors).with( { environment: [ { cc: 'gcc' } ] }, anything ).ordered
909
909
  expect( @configurator.project_config_hash ).to eq( flattened )
910
910
  end
911
911
 
912
+ it "copies the vendor files the requested build tasks need" do
913
+ @configurator.test_build_tasks = true
914
+ @configurator.release_build_tasks = true
915
+
916
+ @configurator.build( 'lib', 'logs', { a: 1 } )
917
+
918
+ expect( @configurator_setup ).to have_received(:vendor_frameworks_and_support_files).with( 'lib', { flat_a: 1 }, test_build: true, release_build: true )
919
+ end
920
+
912
921
  it "redefines an existing element and its constant" do
913
922
  @configurator.build( 'lib', 'logs', { a: 1 } )
914
923
 
@@ -6,6 +6,7 @@
6
6
  # =========================================================================
7
7
 
8
8
  require 'spec_helper'
9
+ require 'tmpdir'
9
10
  require 'ceedling/file_wrapper'
10
11
  require 'ceedling/system_wrapper'
11
12
  require 'ceedling/exceptions'
@@ -215,49 +216,87 @@ describe FileWrapper do
215
216
  # a vendor destination briefly reporting the wrong file/directory type mid-copy).
216
217
  # Stub-based, matching #open_with_retry's own spec above -- no real filesystem or
217
218
  # timing involved.
218
- describe '#cp_r_with_retry' do
219
+ # A transient error mid-copy (a path briefly the wrong type, a file another process holds
220
+ # open on Windows) is retried; one that never clears names the destination.
221
+ describe '#sync_tree retries' do
219
222
  before(:each) do
220
223
  allow(@file_wrapper).to receive(:sleep)
221
224
  end
222
225
 
223
- it 'behaves identically to #cp_r when nothing fails' do
224
- allow(FileUtils).to receive(:cp_r)
226
+ def with_tree
227
+ Dir.mktmpdir do |dir|
228
+ source = File.join( dir, 'src' )
229
+ FileUtils.mkdir_p( source )
230
+ File.write( File.join( source, 'unity.c' ), "int x;\n" )
231
+ yield source, File.join( dir, 'dest' )
232
+ end
233
+ end
225
234
 
226
- @file_wrapper.cp_r_with_retry('/src/.', '/dest')
235
+ it 'copies without retrying when nothing fails' do
236
+ with_tree do |source, dest|
237
+ @file_wrapper.sync_tree( source, dest )
227
238
 
228
- expect(FileUtils).to have_received(:cp_r).with('/src/.', '/dest')
229
- expect(@file_wrapper).not_to have_received(:sleep)
239
+ expect( File.read( File.join( dest, 'unity.c' ) ) ).to eq( "int x;\n" )
240
+ expect(@file_wrapper).not_to have_received(:sleep)
241
+ end
230
242
  end
231
243
 
232
- it 'retries past a SystemCallError and succeeds once the underlying copy stops raising' do
233
- call_count = 0
234
- allow(FileUtils).to receive(:cp_r) do
235
- call_count += 1
236
- raise Errno::EISDIR, 'transient' if call_count < 3
237
- end
244
+ it 'retries past a SystemCallError and succeeds once the copy stops raising' do
245
+ with_tree do |source, dest|
246
+ calls = 0
247
+ allow(@file_wrapper).to receive(:replace_file).and_wrap_original do |original, *args|
248
+ calls += 1
249
+ raise Errno::EISDIR, 'transient' if calls < 3
250
+ original.call( *args )
251
+ end
238
252
 
239
- @file_wrapper.cp_r_with_retry('/src/.', '/dest')
253
+ @file_wrapper.sync_tree( source, dest )
240
254
 
241
- expect(call_count).to eq(3)
242
- expect(@file_wrapper).to have_received(:sleep).twice
255
+ expect( calls ).to eq( 3 )
256
+ expect(@file_wrapper).to have_received(:sleep).twice
257
+ expect( File.exist?( File.join( dest, 'unity.c' ) ) ).to be(true)
258
+ end
243
259
  end
244
260
 
245
- it 'raises a CeedlingException naming the destination and retry count once every retry is exhausted, and logs it' do
246
- allow(FileUtils).to receive(:cp_r).and_raise(Errno::ENOTDIR, 'still wrong type')
261
+ it 'raises a CeedlingException naming the destination once every retry is exhausted, and logs it' do
262
+ with_tree do |source, dest|
263
+ allow(@file_wrapper).to receive(:replace_file).and_raise( Errno::ENOTDIR, 'still wrong type' )
247
264
 
248
- expect { @file_wrapper.cp_r_with_retry('/src/.', '/dest') }.to raise_error(CeedlingException, /\/dest/)
249
- expect(@loginator).to have_received(:log).with(a_string_including('/dest'), Verbosity::ERRORS)
250
- # Initial attempt + one retry per configured delay.
251
- expect(FileUtils).to have_received(:cp_r).exactly(FileWrapper::TRANSIENT_IO_RETRY_DELAYS.size + 1).times
265
+ expect { @file_wrapper.sync_tree( source, dest ) }.to raise_error( CeedlingException, /#{Regexp.escape( dest )}/ )
266
+ expect(@loginator).to have_received(:log).with( a_string_including( dest ), Verbosity::ERRORS )
267
+ # Initial attempt plus one retry for each configured delay
268
+ expect(@file_wrapper).to have_received(:replace_file).exactly( FileWrapper::TRANSIENT_IO_RETRY_DELAYS.size + 1 ).times
269
+ end
252
270
  end
253
271
 
254
- it 'does not retry a non-SystemCallError -- propagates immediately' do
255
- allow(FileUtils).to receive(:cp_r).and_raise(ArgumentError, 'not transient')
272
+ it 'propagates an error that is not a SystemCallError without retrying' do
273
+ with_tree do |source, dest|
274
+ allow(@file_wrapper).to receive(:replace_file).and_raise( ArgumentError, 'not transient' )
256
275
 
257
- expect {
258
- @file_wrapper.cp_r_with_retry('/src/.', '/dest')
259
- }.to raise_error(ArgumentError, 'not transient')
260
- expect(@file_wrapper).not_to have_received(:sleep)
276
+ expect { @file_wrapper.sync_tree( source, dest ) }.to raise_error( ArgumentError, 'not transient' )
277
+ expect(@file_wrapper).not_to have_received(:sleep)
278
+ end
279
+ end
280
+ end
281
+
282
+ # The temporary copy beside the destination is the longest path replace_file writes
283
+ describe '#replace_file' do
284
+ it 'checks the length of its temporary path, then leaves only the destination' do
285
+ Dir.mktmpdir do |dir|
286
+ source = File.join( dir, 'unity.h' )
287
+ destination = File.join( dir, 'out', 'unity.h' )
288
+ File.write( source, "int x;\n" )
289
+ FileUtils.mkdir_p( File.dirname( destination ) )
290
+ checked = []
291
+ allow(@file_wrapper).to receive(:check_path_length) { |path, **| checked << path }
292
+
293
+ @file_wrapper.replace_file( source, destination )
294
+
295
+ expect( checked.size ).to eq( 1 )
296
+ expect( File.dirname( checked.first ) ).to eq( File.dirname( destination ) )
297
+ expect( checked.first.length ).to be > destination.length
298
+ expect( Dir.children( File.dirname( destination ) ) ).to eq( ['unity.h'] )
299
+ end
261
300
  end
262
301
  end
263
302