Class: Partializer
- Includes:
- Partials
- Defined in:
- lib/ceedling/partials/partializer.rb
Constant Summary
Constants included from Partials
Partials::ACCUMULATE, Partials::DEDUCT, Partials::MOCK_PRIVATE, Partials::MOCK_PUBLIC, Partials::PRIVATE, Partials::PUBLIC, Partials::TEST_PRIVATE, Partials::TEST_PUBLIC
Instance Method Summary collapse
-
#extract_implementation_functions(test:, partial:, definitions:, config:) ⇒ Array<Partials::FunctionDefinition>
Returns ArrayPartials::FunctionDefinition for the testable partial implementation.
-
#extract_interface_functions(test:, partial:, definitions:, declarations:, config:) ⇒ Array<Partials::FunctionDeclaration>
Returns ArrayPartials::FunctionDeclaration for the mockable partial interface.
-
#extract_module_contents(name, config, fallback) ⇒ CExtractorTypes::CModule
Extracts and combines C code contents from header and source files.
- #populate_filepaths(configs) ⇒ Object
-
#remap_implementation_header_includes(name:, includes:, partials:, test: nil) ⇒ Object
When
test:is provided, logs the resulting includes at OBNOXIOUS. -
#remap_implementation_source_includes(name:, includes:, partials:, test: nil) ⇒ Object
When
test:is provided, logs the resulting includes at OBNOXIOUS. -
#remap_interface_header_includes(name:, includes:, partials:, test: nil) ⇒ Object
When
test:is provided, logs the resulting includes at OBNOXIOUS. - #sanitize(c_module) ⇒ Object
- #setup ⇒ Object
- #validate_config(c_module:, config:, name:) ⇒ Object
- #validate_extracted_functions(name:, partial:, impl:, interface:) ⇒ Object
Methods included from Partials
manufacture_function_declaration, manufacture_function_definition
Instance Method Details
#extract_implementation_functions(test:, partial:, definitions:, config:) ⇒ Array<Partials::FunctionDefinition>
Returns ArrayPartials::FunctionDefinition for the testable partial implementation.
Processes the tests PartialFunctions config against extracted C function definitions:
PUBLIC -- initial list is all non-private functions; additions inject named private functions
PRIVATE -- initial list is all private functions; additions inject named public functions
ACCUMULATE -- initial list is empty; additions fill it entirely
nil -- returns nil
Subtractions remove named functions from the assembled list. Any functions in mocks.additions are also removed from the final result. Parameters are expected to be pre-validated (no unknown names, no overlap, etc.).
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/ceedling/partials/partializer.rb', line 273 def extract_implementation_functions(test:, partial:, definitions:, config:) pf = config.tests return nil if pf.type.nil? @loginator.log( "Extracting testable Partial functions for #{test}::#{partial}: " \ "type=#{pf.type} additions=#{pf.additions} subtractions=#{pf.subtractions}", Verbosity::DEBUG ) # Build initial list by visibility; ACCUMULATE yields [] funcs = @helper.filter_and_transform_funcs(definitions, pf.type, :impl) # Additions: only search definitions — code_block required for impl transform pf.additions.each do |name| next if funcs.any? { |f| f.name == name } func = @helper.find_and_transform_func( name: name, primary_funcs: definitions, secondary_funcs: [], output_type: :impl ) funcs << func if func end # Subtractions: remove named functions from list result = @helper.subtract_funcs(funcs: funcs, names: pf.subtractions) if !funcs.empty? && result.empty? @loginator.log( "Partial #{test}::#{partial} ⏩️ Subtractions left no testable functions", Verbosity::COMPLAIN, LogLabels::NOTICE ) end # Remove any functions explicitly claimed by the mock side result = @helper.subtract_funcs(funcs: result, names: config.mocks.additions) _log_impl_functions(test, partial, result) return result end |
#extract_interface_functions(test:, partial:, definitions:, declarations:, config:) ⇒ Array<Partials::FunctionDeclaration>
Returns ArrayPartials::FunctionDeclaration for the mockable partial interface.
Processes the mocks PartialFunctions config against extracted C functions:
PUBLIC -- initial list is all non-private functions; additions inject named private functions
PRIVATE -- initial list is all private functions; additions inject named public functions
ACCUMULATE -- initial list is empty; additions fill it entirely
nil -- returns nil
Subtractions remove named functions from the assembled list. Any functions in tests.additions are also removed from the final result. Parameters are expected to be pre-validated (no unknown names, no overlap, etc.). Additions search definitions first, then declarations; only the first match is used.
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/ceedling/partials/partializer.rb', line 334 def extract_interface_functions(test:, partial:, definitions:, declarations:, config:) pf = config.mocks return nil if pf.type.nil? @loginator.log( "Extracting mockable Partial functions for #{test}::#{partial}: " \ "type=#{pf.type} additions=#{pf.additions} subtractions=#{pf.subtractions}", Verbosity::DEBUG ) # Build initial list by visibility; ACCUMULATE yields [] funcs = @helper.filter_and_transform_funcs(definitions, pf.type, :interface) # Additions: search definitions first, then declarations pf.additions.each do |name| next if funcs.any? { |f| f.name == name } func = @helper.find_and_transform_func( name: name, primary_funcs: definitions, secondary_funcs: declarations, output_type: :interface ) funcs << func if func end # Subtractions: remove named functions from list result = @helper.subtract_funcs(funcs: funcs, names: pf.subtractions) if !funcs.empty? && result.empty? @loginator.log( "Partial #{test}::#{partial} ⏩️ Subtractions left no mockable signatures", Verbosity::COMPLAIN, LogLabels::NOTICE ) end # Remove any functions explicitly claimed by the test side result = @helper.subtract_funcs(funcs: result, names: config.tests.additions) _log_interface_functions(test, partial, result) return result end |
#extract_module_contents(name, config, fallback) ⇒ CExtractorTypes::CModule
The method always starts with an empty CModule and merges in contents from any provided files using the CModule's + operator for combining structures.
Extracts and combines C code contents from header and source files
This method uses CExtractor to parse C files and extract their contents including function definitions, function declarations, and variable declarations. If both header and source files are provided, their contents are merged into a single CModule structure.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/ceedling/partials/partializer.rb', line 201 def extract_module_contents(name, config, fallback) # Array for CModule structs contents = [CExtractorTypes::CModule.new()] # Process the C module source and/or header associated with the Partial config [config.header, config.source].zip(['header', 'source']).each do |c_file, file_type| # Do nothing if there's no directives-only preprocessed filepath (e.g. no source only header for a Partial mock) next unless c_file.directives_only_filepath c_module = @c_extractor.from_file( c_file.directives_only_filepath ) _log_module_contents(name, config.module, file_type, c_module) # Update function signatures from fully preprocessed output when available. # Replaces signature/decorators/signature_stripped (but NOT code_block) so that # macros wrapping `static` and `inline` are resolved before visibility filtering. if c_file.full_expansion_filepath @helper.update_signatures_from_full_expansion( funcs: c_module.function_definitions, full_expansion_filepath: c_file.full_expansion_filepath, name: name, module_name: config.module, file_type: file_type ) end # Align extracted function definitions with line markers in preprocessor output. # This perfectly remaps functions found in expanded preprocessor output with # original source location. # This routine depends on original, unaltered function definitions. @helper.associate_function_line_numbers( name: name, funcs: c_module.function_definitions, filepath: c_file.filepath, fallback: fallback ) # 1. Find any function-scope static variable declarations. # 2. Replace them in function definitions with no-ops (for proper coverage reporting). # 3. Promote the function-scoped variables to be module-level variables. decls = @helper.extract_function_scope_static_vars( c_module.function_definitions, name: name, module_name: config.module, file_type: file_type ) c_module.variable_declarations.concat(decls) c_module.element_sequence.concat(decls) unless decls.empty? contents << c_module end # Use `+` operator for CModule to merge everything contents = contents.reduce(&:+) return contents end |
#populate_filepaths(configs) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/ceedling/partials/partializer.rb', line 71 def populate_filepaths(configs) configs.each do |_module, config| # Every partial involves processing header files config.header.filepath = @file_finder.find_header_file(_module, :ignore) # Source file not needed only when mocking public functions exclusively unless !config.tests.present? && config.mocks.type == PUBLIC config.source.filepath = @file_finder.find_source_file(_module, :ignore) end end return configs end |
#remap_implementation_header_includes(name:, includes:, partials:, test: nil) ⇒ Object
When test: is provided, logs the resulting includes at OBNOXIOUS.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/ceedling/partials/partializer.rb', line 86 def remap_implementation_header_includes(name:, includes:, partials:, test: nil) _includes = includes.clone() # Get list of all partialized module names partialized_modules = partials.keys # Remove includes for all partialized modules # Remove our own orginal name as well _includes = remove_matching_includes( includes: _includes, modules: ([name] + partialized_modules) ) # Remove any duplicates Includes.sanitize!(_includes) @loginator.log_list( _includes, "Header includes to inject for testable Partial #{test}::#{name}:", Verbosity::OBNOXIOUS ) if test return _includes end |
#remap_implementation_source_includes(name:, includes:, partials:, test: nil) ⇒ Object
When test: is provided, logs the resulting includes at OBNOXIOUS.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/ceedling/partials/partializer.rb', line 112 def remap_implementation_source_includes(name:, includes:, partials:, test: nil) _includes = includes.clone() # Add implementation header _includes << UserInclude.new( @file_path_utils.form_partial_implementation_header_filename(name) ) mockable_modules = [] partials.each do |_module, config| # Remap mockable interface headers that will be injected into generated partial implementation if includes.any? { |include| include.filename.ext().downcase() == _module.downcase() } if [PUBLIC, PRIVATE].include?( config.mocks.type ) # Insert mockable interface header from remapping of module name _includes << UserInclude.new( @file_path_utils.form_partial_interface_header_filename(_module) ) # Remember the module for later removal of original header mockable_modules << _module end end end # Remove the original module header now that it's remapped to mockable interface # Remove our own orginal name as well _includes = remove_matching_includes( includes: _includes, modules: ([name] + mockable_modules) ) # Remove any duplicates Includes.sanitize!(_includes) @loginator.log_list( _includes, "Source includes to inject for testable Partial #{test}::#{name}:", Verbosity::OBNOXIOUS ) if test return _includes end |
#remap_interface_header_includes(name:, includes:, partials:, test: nil) ⇒ Object
When test: is provided, logs the resulting includes at OBNOXIOUS.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/ceedling/partials/partializer.rb', line 156 def remap_interface_header_includes(name:, includes:, partials:, test: nil) _includes = includes.clone() # Get list of all partialized module names partialized_modules = partials.keys # Remove includes for all partialized modules # Remove our own orginal name as well _includes = remove_matching_includes( includes: _includes, modules: ([name] + partialized_modules) ) # Remove any duplicates Includes.sanitize!(_includes) @loginator.log_list( _includes, "Header includes to inject for mockable Partial #{test}::#{name}:", Verbosity::OBNOXIOUS ) if test return _includes end |
#sanitize(c_module) ⇒ Object
37 38 39 40 41 42 43 44 |
# File 'lib/ceedling/partials/partializer.rb', line 37 def sanitize(c_module) # Remove macro definitions that contain the CEEDLING_GENERATED sentinel string. # These are include-guard and boilerplate macros injected into Ceedling-generated header files. removed = c_module.macro_definitions.select { |m| m.text.include?(CEEDLING_GENERATED) } # Remove from both the macro_definitions list and the element_sequence that references it c_module.macro_definitions.reject! { |m| m.text.include?(CEEDLING_GENERATED) } c_module.element_sequence.reject! { |e| removed.include?(e) } end |
#setup ⇒ Object
24 25 26 27 |
# File 'lib/ceedling/partials/partializer.rb', line 24 def setup() # Alias @helper = @partializer_helper end |
#validate_config(c_module:, config:, name:) ⇒ Object
29 30 31 32 33 34 35 |
# File 'lib/ceedling/partials/partializer.rb', line 29 def validate_config(c_module:, config:, name:) msg = @reportinator.generate_progress("Validating Partial config for '#{name}'") @loginator.log(msg, Verbosity::DEBUG) @helper.validate_function_names_exist(c_module, config, name) @helper.validate_no_additions_subtractions_overlap(config, name) @helper.validate_additions_subtractions_visibility(c_module, config, name) end |
#validate_extracted_functions(name:, partial:, impl:, interface:) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/ceedling/partials/partializer.rb', line 46 def validate_extracted_functions(name:, partial:, impl:, interface:) # Validation is only meaningful and possible if both references are non-nil. return if impl.nil? || interface.nil? # Validation is only meaningful if both lists have content. return if impl.empty? || interface.empty? impl_names = Set.new(impl.map(&:name)) interface_names = Set.new(interface.map(&:name)) msg = @reportinator.generate_module_progress( module_name: name, filename: partial, operation: 'Validating Partial functions for' ) @loginator.log(msg, Verbosity::DEBUG) overlap = impl_names & interface_names overlap.each do |func_name| raise CeedlingException.new( "#{name}: Partial '#{partial}' ⏩️ Function '#{func_name}' cannot be both testable and mockable" ) end end |