ceedling 1.1.8 → 1.1.10

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/README.md +4 -3
  4. data/docs/Changelog.md +28 -0
  5. data/docs/KnownIssues.md +12 -2
  6. data/docs/SECURITY.md +40 -2
  7. data/docs/mkdocs/development/index.md +11 -0
  8. data/docs/mkdocs/plugins/fff.md +1 -1
  9. data/docs/mkdocs/plugins/gcov/gcovr.md +117 -3
  10. data/docs/mkdocs/plugins/gcov/reportgenerator.md +110 -4
  11. data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +29 -1
  12. data/lib/ceedling/file_path_utils.rb +4 -0
  13. data/lib/ceedling/generators/generator.rb +11 -6
  14. data/lib/ceedling/generators/generator_partials.rb +54 -18
  15. data/lib/ceedling/includes/includes.rb +8 -1
  16. data/lib/ceedling/objects.yml +2 -0
  17. data/lib/ceedling/partials/partializer.rb +204 -96
  18. data/lib/ceedling/test_invoker/test_build_executor.rb +21 -7
  19. data/lib/version.rb +1 -1
  20. data/site-local/development/index.html +7 -0
  21. data/site-local/plugins/fff.html +1 -1
  22. data/site-local/plugins/gcov/gcovr.html +156 -3
  23. data/site-local/plugins/gcov/reportgenerator.html +201 -14
  24. data/site-local/sitemap.xml.gz +0 -0
  25. data/spec/system/partials_types_header_spec.rb +166 -0
  26. data/spec/units/c_extractor/c_extractor_integration_spec.rb +18 -0
  27. data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +77 -0
  28. data/spec/units/generators/generator_partials_spec.rb +130 -17
  29. data/spec/units/includes/includes_spec.rb +21 -3
  30. data/spec/units/partials/partializer_spec.rb +225 -0
  31. data/vendor/unity/auto/generate_test_runner.rb +3 -1
  32. data/vendor/unity/auto/unity_test_summary.rb +2 -2
  33. data/vendor/unity/docs/UnityChangeLog.md +7 -0
  34. data/vendor/unity/docs/UnityConfigurationGuide.md +4 -0
  35. data/vendor/unity/src/unity_internals.h +21 -7
  36. data/vendor/unity/test/Makefile +9 -2
  37. data/vendor/unity/test/rakefile_helper.rb +1 -0
  38. data/vendor/unity/test/tests/test_generate_test_runner.rb +49 -0
  39. data/vendor/unity/test/tests/test_unity_test_summary.rb +58 -0
  40. metadata +5 -2
@@ -21,7 +21,8 @@ class GeneratorPartials
21
21
  source_includes:,
22
22
  header_includes:,
23
23
  c_module:,
24
- output_path:
24
+ output_path:,
25
+ include_guard: nil
25
26
  )
26
27
  source = @file_path_utils.form_partial_implementation_source_filename(name)
27
28
  header = @file_path_utils.form_partial_implementation_header_filename(name)
@@ -34,7 +35,10 @@ class GeneratorPartials
34
35
  # write, which would alter any line ending already present in that
35
36
  # content instead of passing it through unchanged.
36
37
  @file_wrapper.open(header_filepath, 'wb') do |file|
37
- generate_header(file, header, header_includes, function_definitions, c_module, true)
38
+ generate_header(
39
+ file, header, header_includes, function_definitions, c_module,
40
+ include_variables: true, include_guard: include_guard
41
+ )
38
42
  end
39
43
 
40
44
  @file_wrapper.open(source_filepath, 'wb') do |file|
@@ -44,13 +48,16 @@ class GeneratorPartials
44
48
  return source_filepath
45
49
  end
46
50
 
47
- def generate_interface(test:, name:, function_declarations:, includes:, c_module:, output_path:)
51
+ def generate_interface(test:, name:, function_declarations:, includes:, c_module:, output_path:, include_guard: nil)
48
52
  header = @file_path_utils.form_partial_interface_header_filename(name)
49
53
  filepath = File.join(output_path, header)
50
54
 
51
55
  # Binary mode: see generate_implementation above.
52
56
  @file_wrapper.open(filepath, 'wb') do |file|
53
- generate_header(file, header, includes, function_declarations, c_module, false)
57
+ generate_header(
58
+ file, header, includes, function_declarations, c_module,
59
+ include_variables: false, include_guard: include_guard
60
+ )
54
61
  end
55
62
 
56
63
  return filepath
@@ -69,7 +76,7 @@ class GeneratorPartials
69
76
  # @param c_module [CExtractorTypes::CModule] Merged module with type_definitions/aggregate_definitions
70
77
  # @param output_path [String] Directory shared with the implementation and interface headers
71
78
  # @return [String, nil] The bare filename (for use as a sibling #include), or nil if nothing was generated
72
- def generate_types(name:, c_module:, output_path:)
79
+ def generate_types(name:, c_module:, output_path:, includes: [], include_guard: nil)
73
80
  return nil if c_module.type_definitions.empty? && c_module.aggregate_definitions.empty?
74
81
 
75
82
  header = @file_path_utils.form_partial_types_header_filename(name)
@@ -78,8 +85,7 @@ class GeneratorPartials
78
85
  # Binary mode: see generate_implementation above.
79
86
  @file_wrapper.open(filepath, 'wb') do |file|
80
87
  guard = FileWrapper.generate_include_guard(header)
81
- file << "#ifndef #{guard}\n"
82
- file << "#define #{guard}\n\n"
88
+ emit_preamble( file, guard, include_guard, includes )
83
89
 
84
90
  anything_emitted = false
85
91
  pending_macros = []
@@ -88,7 +94,10 @@ class GeneratorPartials
88
94
  next unless item.is_a?(CExtractorTypes::CStatement)
89
95
 
90
96
  if c_module.macro_definitions.include?(item)
91
- pending_macros << item
97
+ # The guard emitted above also arrives among the extracted macros, since the real
98
+ # header's own `#define` survives preprocessing. Carrying it a second time is legal
99
+ # but puts a pointless duplicate in every generated types header.
100
+ pending_macros << item unless guard_macro?(item, include_guard)
92
101
  next
93
102
  end
94
103
 
@@ -116,6 +125,38 @@ class GeneratorPartials
116
125
 
117
126
  private
118
127
 
128
+ # Everything above a generated header's own content: the file's include guard, the spoof of the
129
+ # real module header's guard, and the includes that header carries. Shared by all three
130
+ # generated headers, which open identically.
131
+ #
132
+ # The order of the last two is the whole point. A carried header can transitively reach the
133
+ # real module header, so the spoof has to be satisfied before any include is processed rather
134
+ # than after. And relocated content names things a generated file does not define, so without
135
+ # the includes it compiles only where something earlier in the translation unit happened to
136
+ # supply those names -- which is what made the types header's placement a conflict between two
137
+ # correct requirements rather than a choice.
138
+ def emit_preamble(io, guard, include_guard, includes)
139
+ io << "#ifndef #{guard}\n"
140
+ io << "#define #{guard}\n\n"
141
+
142
+ # A module header with no guard of its own supplies nothing to spoof.
143
+ io << "#define #{include_guard}\n\n" if include_guard
144
+
145
+ return if includes.empty?
146
+
147
+ includes.each { |include| io << "#{include}\n" }
148
+ io << "\n"
149
+ end
150
+
151
+ # Whether a macro statement is nothing but the definition of `include_guard`. Anchored so a
152
+ # macro whose name merely begins with the guard's name is never mistaken for it, and so a
153
+ # guard-named macro carrying an actual value is left alone.
154
+ def guard_macro?(item, include_guard)
155
+ return false if include_guard.nil?
156
+
157
+ item.text.match?(/\A\s*#\s*define\s+#{Regexp.escape( include_guard )}\s*\z/)
158
+ end
159
+
119
160
  # A typedef or a non-typedef struct/enum/union tag definition establishes a type; C treats
120
161
  # a second definition of the same type in one translation unit as a redefinition error even
121
162
  # when the two definitions are textually identical. A macro statement carries no such
@@ -159,17 +200,9 @@ class GeneratorPartials
159
200
  # @param function_list [Array] Pre-filtered Partials function objects (respond to :name and :signature)
160
201
  # @param c_module [CExtractorTypes::CModule] Merged module with element_sequence
161
202
  # @param include_variables [Boolean] True for implementation header (emits extern vars); false for interface
162
- def generate_header(io, name, includes, function_list, c_module, include_variables)
203
+ def generate_header(io, name, includes, function_list, c_module, include_variables:, include_guard: nil)
163
204
  guard = FileWrapper.generate_include_guard( name )
164
-
165
- io << "#ifndef #{guard}\n"
166
- io << "#define #{guard}\n\n"
167
-
168
- includes.each do |include|
169
- io << "#{include}\n"
170
- end
171
-
172
- io << "\n" if !includes.empty?
205
+ emit_preamble( io, guard, include_guard, includes )
173
206
 
174
207
  func_by_name = function_list.to_h { |f| [f.name, f] }
175
208
  emitted_funcs = {}
@@ -192,6 +225,9 @@ class GeneratorPartials
192
225
  # generate_types instead of here, so that content defines a type exactly once no
193
226
  # matter how many of a module's generated headers end up in the same test file.
194
227
  next if type_defining?(item, c_module)
228
+ # The spoof above already states this one deliberately; carrying it again would put a
229
+ # pointless duplicate in the file.
230
+ next if guard_macro?(item, include_guard)
195
231
  io << item.text << "\n"
196
232
  last_was_func = false
197
233
  anything_emitted = true
@@ -254,8 +254,15 @@ class Includes
254
254
  return _includes
255
255
  end
256
256
 
257
+ # Ruby's sort/sort_by family does not guarantee a stable sort -- the relative order
258
+ # of elements a comparator treats as equal (every UserInclude here, all mapping to
259
+ # the same "1") is free to come out differently across Ruby versions and platforms.
260
+ # partition sidesteps the question entirely: it's a simple filter, not a comparator
261
+ # sort, so each group's own original relative order is preserved by definition, on
262
+ # every Ruby version and platform, with nothing to be unstable about.
257
263
  def self.sort!(includes)
258
- includes.sort_by! { |include| include.is_a?(SystemInclude) ? 0 : 1 }
264
+ system, other = includes.partition { |include| include.is_a?(SystemInclude) }
265
+ includes.replace(system + other)
259
266
  return includes
260
267
  end
261
268
  end
@@ -383,6 +383,8 @@ partializer:
383
383
  - file_finder
384
384
  - c_extractor
385
385
  - file_path_utils
386
+ - preprocessinator_reconstructor
387
+ - file_wrapper
386
388
  - reportinator
387
389
  - loginator
388
390
 
@@ -14,12 +14,18 @@ require 'ceedling/c_extractor/c_extractor'
14
14
  require 'ceedling/c_extractor/c_extractor_constants'
15
15
  require 'ceedling/c_extractor/c_extractor_types'
16
16
  require 'ceedling/constants'
17
+ require 'ceedling/encodinator'
17
18
 
18
19
  class Partializer
19
20
 
21
+ # Enough of a header to reach its include guard. The guard pair sits at the top of a header by
22
+ # construction, so a bounded read beats loading a large file to find it.
23
+ GUARD_SCAN_BYTES = 2048
24
+
20
25
  include Partials
21
26
 
22
- constructor :partializer_helper, :file_finder, :c_extractor, :file_path_utils, :reportinator, :loginator
27
+ constructor :partializer_helper, :file_finder, :c_extractor, :file_path_utils,
28
+ :preprocessinator_reconstructor, :file_wrapper, :reportinator, :loginator
23
29
 
24
30
  def setup()
25
31
  # Alias
@@ -82,115 +88,89 @@ class Partializer
82
88
  return configs
83
89
  end
84
90
 
91
+ # When `test:` is provided, logs the resulting includes at OBNOXIOUS.
92
+ # Includes for the generated implementation header. This module's own header is replaced by
93
+ # the shared types header at its original list position; every other partialized module's
94
+ # include is dropped.
95
+ #
85
96
  # When `test:` is provided, logs the resulting includes at OBNOXIOUS.
86
97
  def remap_implementation_header_includes(name:, includes:, partials:, types_header: nil, test: nil)
87
- _includes = includes.clone()
88
-
89
- # Get list of all partialized module names
90
- partialized_modules = partials.keys
91
-
92
- # Remove includes for all partialized modules
93
- # Remove our own orginal name as well
94
- _includes = remove_matching_includes(
95
- includes: _includes,
96
- modules: ([name] + partialized_modules)
98
+ _remap_includes(
99
+ name: name,
100
+ includes: includes,
101
+ partials: partials,
102
+ replacement: types_header,
103
+ others: :drop,
104
+ noun: 'Header includes to inject for testable Partial',
105
+ test: test
97
106
  )
98
-
99
- # When this module has any typedefs or aggregate definitions, they live in a shared
100
- # header generated once and included here rather than duplicated inline.
101
- _includes << UserInclude.new(types_header) if types_header
102
-
103
- # Remove any duplicates
104
- Includes.sanitize!(_includes)
105
-
106
- @loginator.log_list(
107
- _includes,
108
- "Header includes to inject for testable Partial #{test}::#{name}:",
109
- Verbosity::OBNOXIOUS
110
- ) if test
111
-
112
- return _includes
113
107
  end
114
108
 
109
+ # Includes for the generated mockable interface header. Same treatment as the implementation
110
+ # header: both files carry the shared types header, which is how a module tested and mocked
111
+ # in one test file still gets exactly one definition of each of its types.
112
+ #
115
113
  # When `test:` is provided, logs the resulting includes at OBNOXIOUS.
116
- def remap_implementation_source_includes(name:, includes:, partials:, test: nil)
117
- _includes = includes.clone()
118
-
119
- # Add implementation header
120
- _includes << UserInclude.new(
121
- @file_path_utils.form_partial_implementation_header_filename(name)
114
+ def remap_interface_header_includes(name:, includes:, partials:, types_header: nil, test: nil)
115
+ _remap_includes(
116
+ name: name,
117
+ includes: includes,
118
+ partials: partials,
119
+ replacement: types_header,
120
+ others: :drop,
121
+ noun: 'Header includes to inject for mockable Partial',
122
+ test: test
122
123
  )
124
+ end
123
125
 
124
- mockable_modules = []
125
-
126
- partials.each do |_module, config|
127
- # Remap mockable interface headers that will be injected into generated partial implementation
128
- #
129
- # Any real (non-nil) mock mode means this module has a generated mock interface header
130
- # to redirect to -- PUBLIC/PRIVATE alone once covered every mode that existed, but DEDUCT
131
- # (MOCK_PARTIAL_ALL_MODULE) and ACCUMULATE (MOCK_PARTIAL_MODULE) were added later without
132
- # updating this check, so a module mocked via either of those was silently never
133
- # redirected: its real header (and any static inline/static function bodies in it) stayed
134
- # #include'd verbatim, compiling straight past the mock instead of being replaced by it.
135
- if includes.any? { |include| include.filename.ext().downcase() == _module.downcase() }
136
- if !config.mocks.type.nil?
137
- # Insert mockable interface header from remapping of module name
138
- _includes << UserInclude.new(
139
- @file_path_utils.form_partial_interface_header_filename(_module)
140
- )
141
- # Remember the module for later removal of original header
142
- mockable_modules << _module
143
- end
144
- end
145
- end
146
-
147
- # Remove the original module header now that it's remapped to mockable interface
148
- # Remove our own orginal name as well
149
- _includes = remove_matching_includes(
150
- includes: _includes,
151
- modules: ([name] + mockable_modules)
126
+ # Includes for the generated implementation source. This module's own header is replaced by
127
+ # the generated implementation header, which carries the types header internally. Another
128
+ # partialized module that is mocked is redirected to its generated interface header; one that
129
+ # is not mocked keeps its real header, which the generated code still needs.
130
+ #
131
+ # When `test:` is provided, logs the resulting includes at OBNOXIOUS.
132
+ def remap_implementation_source_includes(name:, includes:, partials:, test: nil)
133
+ _remap_includes(
134
+ name: name,
135
+ includes: includes,
136
+ partials: partials,
137
+ replacement: @file_path_utils.form_partial_implementation_header_filename(name),
138
+ others: :mock_or_keep,
139
+ noun: 'Source includes to inject for testable Partial',
140
+ test: test
152
141
  )
153
-
154
- # Remove any duplicates
155
- Includes.sanitize!(_includes)
156
-
157
- @loginator.log_list(
158
- _includes,
159
- "Source includes to inject for testable Partial #{test}::#{name}:",
160
- Verbosity::OBNOXIOUS
161
- ) if test
162
-
163
- return _includes
164
142
  end
165
143
 
144
+ # Includes the shared types header carries so the extracted types resolve wherever that file
145
+ # lands. Nothing replaces this module's own header here -- the types header cannot include
146
+ # itself -- so it is simply dropped. Another partialized module is redirected to its generated
147
+ # interface header when mocked and dropped otherwise: its real header would reintroduce the
148
+ # very content that module's own Partial replaced.
149
+ #
166
150
  # When `test:` is provided, logs the resulting includes at OBNOXIOUS.
167
- def remap_interface_header_includes(name:, includes:, partials:, types_header: nil, test: nil)
168
- _includes = includes.clone()
169
-
170
- # Get list of all partialized module names
171
- partialized_modules = partials.keys
172
-
173
- # Remove includes for all partialized modules
174
- # Remove our own orginal name as well
175
- _includes = remove_matching_includes(
176
- includes: _includes,
177
- modules: ([name] + partialized_modules)
151
+ def remap_types_header_includes(name:, includes:, partials:, test: nil)
152
+ _remap_includes(
153
+ name: name,
154
+ includes: includes,
155
+ partials: partials,
156
+ replacement: nil,
157
+ others: :mock_or_drop,
158
+ noun: 'Dependency includes to carry into the shared types header for Partial',
159
+ test: test
178
160
  )
161
+ end
179
162
 
180
- # When this module has any typedefs or aggregate definitions, they live in a shared
181
- # header generated once and included here rather than duplicated inline.
182
- _includes << UserInclude.new(types_header) if types_header
183
-
184
- # Remove any duplicates
185
- Includes.sanitize!(_includes)
186
-
187
- @loginator.log_list(
188
- _includes,
189
- "Header includes to inject for mockable Partial #{test}::#{name}:",
190
- Verbosity::OBNOXIOUS
191
- ) if test
192
-
193
- return _includes
163
+ # The include guard macro the module's real header defines, or nil when it has none.
164
+ #
165
+ # Read from the original header rather than from the reconstituted copy or from extracted
166
+ # macros. The reconstituted copy carries a synthetic guard that #sanitize strips, so neither is
167
+ # a dependable source. A nil return means the header guards itself some other way --
168
+ # `#pragma once` -- and offers no macro for a generated file to spoof.
169
+ def extract_module_include_guard(filepath)
170
+ return nil if filepath.nil?
171
+
172
+ text = @file_wrapper.read( filepath, GUARD_SCAN_BYTES ).clean_encoding
173
+ @preprocessinator_reconstructor.extract_include_guard( text )
194
174
  end
195
175
 
196
176
  # Extracts and combines C code contents from header and source files
@@ -452,6 +432,134 @@ class Partializer
452
432
  )
453
433
  end
454
434
 
435
+ # One body behind all four public remaps. Each differs only in what replaces this module's
436
+ # own header, what becomes of other partialized modules, and the noun it logs under -- the
437
+ # splice, the sanitize, and the logging are identical in every case.
438
+ #
439
+ # `others` selects the treatment of every OTHER partialized module in the same test file:
440
+ # :drop -- remove its include outright, with no substitute
441
+ # :mock_or_keep -- redirect to its interface header when mocked, else leave its real header
442
+ # :mock_or_drop -- redirect to its interface header when mocked, else drop it
443
+ def _remap_includes(name:, includes:, partials:, replacement:, others:, noun:, test: nil)
444
+ _includes = includes.clone()
445
+
446
+ # A nil replacement strips this module's own header and substitutes nothing.
447
+ _includes = splice_in_replacement( includes: _includes, name: name, replacement: replacement )
448
+
449
+ _includes =
450
+ case others
451
+ when :drop
452
+ remove_matching_includes( includes: _includes, modules: (partials.keys - [name]) )
453
+ when :mock_or_keep, :mock_or_drop
454
+ # The types header differs from the generated source in two ways at once, which is why
455
+ # both flags below read from one condition.
456
+ #
457
+ # It skips this module: the interface header includes the types header, so carrying the
458
+ # interface back in would fold that file's declarations into this one. The generated
459
+ # source needs the opposite -- a module tested and mocked in one test file keeps its
460
+ # public bodies in the implementation and the private declarations they call in the
461
+ # interface, so it needs both.
462
+ #
463
+ # And it drops an unmocked partialized dependency rather than keeping its real header,
464
+ # which would reintroduce content that module's own Partial replaced.
465
+ shared_types_header = ( others == :mock_or_drop )
466
+
467
+ _redirect_mocked_partials(
468
+ includes: _includes,
469
+ original: includes,
470
+ name: name,
471
+ partials: partials,
472
+ drop_unmocked: shared_types_header,
473
+ skip_self: shared_types_header
474
+ )
475
+ else
476
+ PartializerRuntime.raise_on_option( others )
477
+ end
478
+
479
+ # Remove any duplicates
480
+ Includes.sanitize!(_includes)
481
+
482
+ @loginator.log_list(
483
+ _includes,
484
+ "#{noun} #{test}::#{name}:",
485
+ Verbosity::OBNOXIOUS
486
+ ) if test
487
+
488
+ return _includes
489
+ end
490
+
491
+ # Appends the generated interface header for each mocked partialized module the original list
492
+ # named, then removes the real headers those replaced. `skip_self` decides whether the module
493
+ # being generated counts as one of them, which it does everywhere except the types header --
494
+ # see the call site.
495
+ #
496
+ # Mutates `includes`, which always arrives fresh from splice_in_replacement.
497
+ #
498
+ # Any real (non-nil) mock mode counts. PUBLIC/PRIVATE alone once covered every mode that
499
+ # existed, but DEDUCT (MOCK_PARTIAL_ALL_MODULE) and ACCUMULATE (MOCK_PARTIAL_MODULE) were
500
+ # added later without updating the check, so a module mocked via either was silently never
501
+ # redirected: its real header, and any static inline or static function bodies in it, stayed
502
+ # #include'd verbatim and compiled straight past the mock.
503
+ def _redirect_mocked_partials(includes:, original:, name:, partials:, drop_unmocked:, skip_self:)
504
+ retired = []
505
+
506
+ partials.each do |_module, config|
507
+ next if skip_self && _module == name
508
+ next unless original.any? { |include| include.filename.ext().downcase() == _module.downcase() }
509
+
510
+ if config.mocks.type.nil?
511
+ retired << _module if drop_unmocked
512
+ else
513
+ includes << UserInclude.new(
514
+ @file_path_utils.form_partial_interface_header_filename(_module)
515
+ )
516
+ retired << _module
517
+ end
518
+ end
519
+
520
+ remove_matching_includes( includes: includes, modules: retired )
521
+ end
522
+
523
+ # Swaps `name`'s own header include for `replacement` at that same list position,
524
+ # instead of stripping it out and appending the replacement at the very end.
525
+ #
526
+ # Both call sites' generated replacements (the shared types header; the generated
527
+ # implementation header, which itself carries the types header) spoof the real
528
+ # header's own include guard (a top-of-file `#define <ORIGINAL_GUARD>`) so that if the
529
+ # real header is *also* reached a second way -- e.g. a transitively-included,
530
+ # differently-named header that itself does a genuine `#include` of this module's
531
+ # real header -- the real header's guard is already
532
+ # tripped and its content (a second, conflicting copy of the same typedefs/structs)
533
+ # never gets processed. That only works if the spoofing macro is defined *before*
534
+ # such a transitive re-inclusion is reached, not after. Appending at the very end put
535
+ # it after any such re-inclusion instead of before it. Keeping the replacement at the
536
+ # original header's own list position preserves everything the generated content
537
+ # depends on that came before it in the source's own working include order (e.g. a
538
+ # shared Types.h), while still landing ahead of anything transitively re-reaching the
539
+ # real header afterward.
540
+ def splice_in_replacement(includes:, name:, replacement:)
541
+ return remove_matching_includes(includes: includes, modules: [name]) unless replacement
542
+
543
+ replaced = false
544
+ spliced = includes.map do |include|
545
+ if !replaced && include.filename.ext().downcase() == name.downcase()
546
+ replaced = true
547
+ UserInclude.new(replacement)
548
+ else
549
+ include
550
+ end
551
+ end
552
+ # This module's own header wasn't in the includes list at all -- no original
553
+ # position to preserve, so fall back to appending.
554
+ spliced << UserInclude.new(replacement) unless replaced
555
+
556
+ # A duplicate/case-variant entry beyond the first match (e.g. both 'module.h' and
557
+ # 'MODULE.H' present) has no meaningful position of its own to preserve -- drop it
558
+ # like remove_matching_includes always has.
559
+ spliced = remove_matching_includes(includes: spliced, modules: [name])
560
+ spliced
561
+ end
562
+
455
563
  # Remove includes that match the given module names (case-insensitive)
456
564
  # Returns a new array with matching includes removed
457
565
  def remove_matching_includes(includes:, modules:)
@@ -183,15 +183,27 @@ class TestBuildExecutor
183
183
 
184
184
  @partializer.sanitize( module_contents )
185
185
 
186
+ # The real header's guard is spoofed deliberately in every generated file, and the types
187
+ # header carries the dependencies its extracted types name.
188
+ module_include_guard = @partializer.extract_module_include_guard( config.header.filepath )
189
+ types_header_includes = @partializer.remap_types_header_includes(
190
+ name: config.module,
191
+ includes: (config.source.includes + config.header.includes),
192
+ partials: testable.partials.configs,
193
+ test: name
194
+ )
195
+
186
196
  # Generated once and shared by the implementation and interface headers below (via
187
197
  # their own includes lists), so a module tested and mocked in the same test file gets
188
198
  # exactly one C definition of each of its typedefs and aggregate types.
189
199
  types_header = @generator.generate_partial_types(
190
- name: config.module, # Module name, not test name -- two modules Partialed
191
- # in the same test file must not collide on one
192
- # shared types header filename
193
- c_module: module_contents,
194
- output_path: testable.paths[:partials]
200
+ name: config.module, # Module name, not test name -- two modules Partialed
201
+ # in the same test file must not collide on one
202
+ # shared types header filename
203
+ c_module: module_contents,
204
+ output_path: testable.paths[:partials],
205
+ includes: types_header_includes,
206
+ include_guard: module_include_guard
195
207
  )
196
208
 
197
209
  implementation = @partializer.extract_implementation_functions(
@@ -235,7 +247,8 @@ class TestBuildExecutor
235
247
  test: name
236
248
  ),
237
249
  input_filepath: config.source.filepath,
238
- output_path: testable.paths[:partials]
250
+ output_path: testable.paths[:partials],
251
+ include_guard: module_include_guard
239
252
  }
240
253
 
241
254
  unless implementation.nil?
@@ -256,7 +269,8 @@ class TestBuildExecutor
256
269
  ),
257
270
  c_module: module_contents,
258
271
  input_filepath: config.header.filepath,
259
- output_path: testable.paths[:partials]
272
+ output_path: testable.paths[:partials],
273
+ include_guard: module_include_guard
260
274
  }
261
275
 
262
276
  unless interface.nil?
data/lib/version.rb CHANGED
@@ -15,7 +15,7 @@
15
15
  module Ceedling
16
16
  module Version
17
17
  # Convenience constants for gem building, etc.
18
- GEM = '1.1.8'
18
+ GEM = '1.1.10'
19
19
  TAG = GEM
20
20
 
21
21
  # If run as a script print Ceedling's version to $stdout
@@ -4080,6 +4080,13 @@ Please familiarize yourself with it before participating.</p>
4080
4080
  <p>Guidelines for contributing to this project — be it code, reviews,
4081
4081
  documentation, or issue reports.</p>
4082
4082
  </li>
4083
+ <li>
4084
+ <p><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 1 3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5zm0 6c1.4 0 2.8 1.1 2.8 2.5V11c.6 0 1.2.6 1.2 1.3v3.5c0 .6-.6 1.2-1.3 1.2H9.2c-.6 0-1.2-.6-1.2-1.3v-3.5c0-.6.6-1.2 1.2-1.2V9.5C9.2 8.1 10.6 7 12 7m0 1.2c-.8 0-1.5.5-1.5 1.3V11h3V9.5c0-.8-.7-1.3-1.5-1.3"/></svg></span> <strong><a href="https://github.com/ThrowTheSwitch/Ceedling?tab=security-ov-file">Security</a></strong></p>
4085
+ <hr />
4086
+ <p>See the <a href="https://github.com/ThrowTheSwitch/Ceedling/blob/master/docs/SECURITY.md">policy</a> for disclosure details and
4087
+ guidance on issues that vulnerability inspection tools may surface.
4088
+ Report a vulnerability through a <a href="https://github.com/ThrowTheSwitch/Ceedling/security/advisories/new">private advisory</a>.</p>
4089
+ </li>
4083
4090
  </ul>
4084
4091
  </div>
4085
4092
  <h2 id="projects">Projects</h2>
@@ -4424,7 +4424,7 @@
4424
4424
  <h1 id="fake-function-framework-for-ceedling">Fake Function Framework for Ceedling</h1>
4425
4425
  <p>This plugin causes Ceedling to use the <a href="https://github.com/meekrosoft/fff">Fake Function Framework</a> for mocking instead of CMock, the default mocking framework packaged with Ceedling.</p>
4426
4426
  <p>Using <em>FFF</em> provides less strict mocking than CMock and affords more loosely-coupled tests.</p>
4427
- <p>This Ceedling 1.x plugin incorporates a snapshot of <em>FFF</em> version 0.1.1 and supersedes a separately available <a href="https://github.com/ElectronVector/fake_function_framework">FFF Ceedling plugin project</a>. The built-in <em>FFF</em> plugin that now comes with Ceedling was derived from the ElectronVector project and is now maintained along with Ceedling and tracks its updates.</p>
4427
+ <p>This Ceedling 1.x plugin incorporates a snapshot of <em>FFF</em> version 1.1 and supersedes a separately available <a href="https://github.com/ElectronVector/fake_function_framework">FFF Ceedling plugin project</a>. The built-in <em>FFF</em> plugin that now comes with Ceedling was derived from the ElectronVector project and is now maintained along with Ceedling and tracks its updates.</p>
4428
4428
  <div class="admonition note">
4429
4429
  <p class="admonition-title">Special thanks to Matt Chernosky</p>
4430
4430
  <p><a href="http://www.electronvector.com">Matt Chernosky</a> originally developed this plugin