diff --git a/lib/ceedling/configurator_setup.rb b/lib/ceedling/configurator_setup.rb index 1f9e8ee6..090d6204 100644 --- a/lib/ceedling/configurator_setup.rb +++ b/lib/ceedling/configurator_setup.rb @@ -205,7 +205,8 @@ def validate_defines(_config) valid = true - # Validate that each context contains only a list of symbols or a matcher hash for :test context + # Validate that each context contains only a list of symbols or a matcher hash for :test / :preprocess context + # # :defines: # :: # - FOO @@ -218,17 +219,30 @@ def validate_defines(_config) # :: # - FOO # - BAR + # :preprocess: + # :: + # - FOO + # - BAR + defines.each_pair do |context, config| walk = @reportinator.generate_config_walk( [:defines, context] ) - # Special handling for setting, not context + # Special handling for configuration setting, not a hash context container next if context == :use_test_definition - # Non-test contexts - if context != :test - # Handle the (probably) common case of trying to use matchers for any context other than test + # Matcher contexts (only contexts that support matcher hashes) + if context == :test or context == :preprocess + if config.class != Array and config.class != Hash + msg = "#{walk} entry '#{config}' must be a list or matcher, not #{config.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + # All other (simple) contexts + else + # Handle the (probably) common case of trying to use matchers for any context other than :test or :preprocess if config.class == Hash - msg = "#{walk} entry '#{config}' must be a list--matcher hashes only availalbe for the :test context (see docs for details)" + msg = "#{walk} entry '#{config}' must be a list--matcher hashes only availalbe for :test & :preprocess contexts (see docs for details)" @loginator.log( msg, Verbosity::ERRORS ) valid = false # Catchall for any oddball entries @@ -237,13 +251,6 @@ def validate_defines(_config) @loginator.log( msg, Verbosity::ERRORS ) valid = false end - # Test contexts - else - if config.class != Array and config.class != Hash - msg = "#{walk} entry '#{config}' must be a list or matcher, not #{config.class.to_s.downcase} (see docs for examples)" - @loginator.log( msg, Verbosity::ERRORS ) - valid = false - end end end @@ -252,8 +259,9 @@ def validate_defines(_config) # :: # :test, :release, etc. # - FOO # - BAR + defines.each_pair do |context, config| - # Only validate lists of compilation symbols in this block (look for test matchers in next block) + # Only validate lists of compilation symbols in this block (look for matchers in next block) next if config.class != Array # Ensure each item in list is a string @@ -267,52 +275,60 @@ def validate_defines(_config) end end - # Validate test context matchers (hash) if they exist + # Validate :test / :preprocess context matchers (hash) if they exist # :defines: # :test: # :: # Can be wildcard, substring, or regular expression in a string or symbol # - FOO # - BAR + # :preprocess: + # :: # Can be wildcard, substring, or regular expression in a string or symbol + # - FOO + # - BAR - # If there's no test context with a hash of matchers, we're done - return valid if !(defines[:test] and defines[:test].class == Hash) + contexts = [:test, :preprocess] - matchers = defines[:test] + contexts.each do |context| + matchers = defines[context] - walk = @reportinator.generate_config_walk( [:defines, :test] ) + # Skip processing if context isn't present or is present but is not a matcher hash + next if matchers.nil? or matchers.class != Hash - # Inspect each test matcher - matchers.each_pair do |matcher, symbols| + walk = @reportinator.generate_config_walk( [:defines, context] ) - # Ensure matcher itself is a Ruby symbol or string - if matcher.class != Symbol and matcher.class != String - msg = "#{walk} entry '#{matcher}' is not a string or symbol" - @loginator.log( msg, Verbosity::ERRORS ) - valid = false + # Inspect each test matcher + matchers.each_pair do |matcher, symbols| - # Skip further validation if matcher key is not a symbol - next - end + # Ensure matcher itself is a Ruby symbol or string + if matcher.class != Symbol and matcher.class != String + msg = "#{walk} entry '#{matcher}' is not a string or symbol" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false - walk = @reportinator.generate_config_walk( [:defines, :test, matcher] ) + # Skip further validation if matcher key is not a symbol + next + end - # Ensure each item in compilation symbols list for matcher is a string - symbols.each do |symbol| - if symbol.class != String - msg = "#{walk} entry '#{symbol}' is not a string" + walk = @reportinator.generate_config_walk( [:defines, context, matcher] ) + + # Ensure each item in compilation symbols list for matcher is a string + symbols.each do |symbol| + if symbol.class != String + msg = "#{walk} entry '#{symbol}' is not a string" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + + begin + @configurator_validator.validate_matcher( matcher.to_s.strip() ) + rescue Exception => ex + msg = "Matcher #{walk} contains #{ex.message}" @loginator.log( msg, Verbosity::ERRORS ) valid = false end - end - begin - @configurator_validator.validate_matcher( matcher.to_s.strip() ) - rescue Exception => ex - msg = "Matcher #{walk} contains #{ex.message}" - @loginator.log( msg, Verbosity::ERRORS ) - valid = false end - end return valid @@ -339,11 +355,21 @@ def validate_flags(_config) # :: # :test, :release, etc. # :: # :compile, :link, etc. # ... + flags.each_pair do |context, operations| + walk = @reportinator.generate_config_walk( [:flags, context] ) + + if operations.nil? + msg = "#{walk} operations key / value pairs are missing" + @loginator.log( msg, Verbosity::ERRORS ) + + valid = false + next + end + if operations.class != Hash - walk = @reportinator.generate_config_walk( [:flags, context] ) example = @reportinator.generate_config_walk( [:flags, context, :compile] ) - msg = "#{walk} context must contain : key / value pairs, not #{operations.class.to_s.downcase} (ex. #{example})" + msg = "#{walk} context must contain : key / value pairs, not #{operations.class.to_s.downcase} '#{operations}' (ex. #{example})" @loginator.log( msg, Verbosity::ERRORS ) # Immediately bail out @@ -351,28 +377,52 @@ def validate_flags(_config) end end - # Validate that each operation contains only a list of flags or a matcher hash for :test context + if !!flags[:release] and !!flags[:release][:preprocess] + walk = @reportinator.generate_config_walk( [:flags, :release, :preprocess] ) + msg = "Preprocessing configured at #{walk} is only supported in the :test context" + @loginator.log( msg, Verbosity::ERRORS, LogLabels::WARNING ) + end + + # Validate that each <:context> ↳ <:operation> contains only a list of flags or that :test ↳ <:operation> optionally contains a matcher hash + # # :flags: - # :: - # :: + # :: # :test or :release + # :: # :compile, :link, or :assemble (plus :preprocess for :test context) # - --flag # # or # # :flags: # :test: - # :operation: + # :: # :: # - --flag + flags.each_pair do |context, operations| operations.each_pair do |operation, config| - walk = @reportinator.generate_config_walk( [:defines, context, operation] ) + walk = @reportinator.generate_config_walk( [:flags, context, operation] ) + + if config.nil? + msg = "#{walk} is missing a list or matcher hash" + @loginator.log( msg, Verbosity::ERRORS ) - # Non-test contexts - if context != :test - # Handle the (probably) common case of trying to use matchers for any context other than test + valid = false + next + end + + # :test context operations with lists or matchers (hashes) + if context == :test + if config.class != Array and config.class != Hash + msg = "#{walk} entry '#{config}' must be a list or matcher hash, not #{config.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + # Other (simple) contexts + else + # Handle the (probably) common case of trying to use matchers for operations in any context other than :test if config.class == Hash - msg = "#{walk} entry '#{config}' must be a list--matcher hashes only availalbe for the :test context (see docs for details)" + msg = "#{walk} entry '#{config}' must be a list--matcher hashes only availalbe for :test context (see docs for details)" @loginator.log( msg, Verbosity::ERRORS ) valid = false # Catchall for any oddball entries @@ -381,26 +431,20 @@ def validate_flags(_config) @loginator.log( msg, Verbosity::ERRORS ) valid = false end - # Test contexts - else - if config.class != Array and config.class != Hash - msg = "#{walk} entry '#{config}' must be a list or matcher, not #{config.class.to_s.downcase} (see docs for examples)" - @loginator.log( msg, Verbosity::ERRORS ) - valid = false - end end end end - # Validate simple option of lists of flags (strings) for :context ↳ :operation + # Validate simple option of lists of flags (strings) for <:context> ↳ <:operation> # :flags # :: # :: # - --flag + flags.each_pair do |context, operations| operations.each_pair do |operation, flags| - # Only validate lists of flags in this block (look for test matchers in next block) + # Only validate lists of flags in this block (look for matchers in next block) next if flags.class != Array # Ensure each item in list is a string @@ -415,7 +459,7 @@ def validate_flags(_config) end end - # Validate test context matchers (hash) if they exist + # Validate :test ↳ <:operation> matchers (hash) if they exist # :flags: # :test: # :: # :preprocess, :compile, :assemble, :link @@ -423,7 +467,7 @@ def validate_flags(_config) # - FOO # - BAR - # If there's no test context with an operation having a hash of matchers, we're done + # If there's no test context, we're done test_context = flags[:test] return valid if test_context.nil? @@ -435,13 +479,13 @@ def validate_flags(_config) end end - # We found no matchers, so bail out + # If there's no matchers for :test ↳ <:operation>, we're done return valid if !matchers_present - # Inspect each test operation matcher + # Inspect each :test ↳ <:operation> matcher test_context.each_pair do |operation, matchers| # Only validate matchers (skip simple lists of flags) - next if !matchers.class == Hash + next if matchers.class != Hash matchers.each_pair do |matcher, flags| # Ensure matcher itself is a Ruby symbol or string @@ -451,7 +495,7 @@ def validate_flags(_config) @loginator.log( msg, Verbosity::ERRORS ) valid = false - # Skip further validation if matcher key is not a symbol + # Skip further validation if matcher key is not a string or symbol next end diff --git a/lib/ceedling/constants.rb b/lib/ceedling/constants.rb index 973e5afc..7d07f604 100644 --- a/lib/ceedling/constants.rb +++ b/lib/ceedling/constants.rb @@ -125,9 +125,10 @@ class StdErrRedirect UTILS_TASK_ROOT = UTILS_ROOT_NAME + ':' unless defined?(UTILS_TASK_ROOT) UTILS_SYM = UTILS_ROOT_NAME.to_sym unless defined?(UTILS_SYM) -OPERATION_COMPILE_SYM = :compile unless defined?(OPERATION_COMPILE_SYM) -OPERATION_ASSEMBLE_SYM = :assemble unless defined?(OPERATION_ASSEMBLE_SYM) -OPERATION_LINK_SYM = :link unless defined?(OPERATION_LINK_SYM) +OPERATION_PREPROCESS_SYM = :preprocess unless defined?(OPERATION_PREPROCESS_SYM) +OPERATION_COMPILE_SYM = :compile unless defined?(OPERATION_COMPILE_SYM) +OPERATION_ASSEMBLE_SYM = :assemble unless defined?(OPERATION_ASSEMBLE_SYM) +OPERATION_LINK_SYM = :link unless defined?(OPERATION_LINK_SYM) # Match presence of any glob pattern characters diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index b931e096..cd222189 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -333,15 +333,15 @@ :defines => { :use_test_definition => false, - :test => [], # A hash/sub-hashes in config file can include operations and test executable matchers as keys - :preprocess => [], # A hash/sub-hashes in config file can include operations and test executable matchers as keys - :release => [] + :test => [], # List of symbols or matcher hashes with test executables as keys + # :preprocess is identical to :test but lacks a default here as missing vs. empty values have additional meaning + :release => [] # List of symbols only }, :flags => { # Test & release flags are validated for presence--empty flags causes an error - # :test => [], # A hash/sub-hashes in config file can include operations and test executable matchers as keys - # :release => [] # A hash/sub-hashes in config file can include arrays for operations + # :test => {}, # hash/sub-hash of operations containing lists of flags or matcher hashes with test executables as keys + # :release => {} # hash/sub-hashes of operations containing lists of flags }, :libraries => { diff --git a/lib/ceedling/defineinator.rb b/lib/ceedling/defineinator.rb index f04a26c3..fb6ace48 100644 --- a/lib/ceedling/defineinator.rb +++ b/lib/ceedling/defineinator.rb @@ -36,10 +36,10 @@ def defines_defined?(context:) # Defaults to inspecting configurations beneath top-level :defines # (But, we can also lookup defines symbol lists within framework configurations--:unity, :cmock, :cexception) - def defines(topkey:@topkey, subkey:, filepath:nil) + def defines(topkey:@topkey, subkey:, filepath:nil, default:[]) defines = @config_matchinator.get_config(primary:topkey, secondary:subkey) - if defines == nil then return [] + if defines == nil then return default elsif defines.is_a?(Array) then return defines.flatten # Flatten to handle list-nested YAML aliases elsif defines.is_a?(Hash) arg_hash = { diff --git a/lib/ceedling/flaginator.rb b/lib/ceedling/flaginator.rb index 53c380d8..d4bb70b1 100644 --- a/lib/ceedling/flaginator.rb +++ b/lib/ceedling/flaginator.rb @@ -40,10 +40,10 @@ def flags_defined?(context:, operation:nil) return @config_matchinator.config_include?(primary:@section, secondary:context, tertiary:operation) end - def flag_down(context:, operation:, filepath:nil) + def flag_down(context:, operation:, filepath:nil, default:[]) flags = @config_matchinator.get_config(primary:@section, secondary:context, tertiary:operation) - if flags == nil then return [] + if flags == nil then return default elsif flags.is_a?(Array) then return flags.flatten # Flatten to handle list-nested YAML aliases elsif flags.is_a?(Hash) arg_hash = { diff --git a/lib/ceedling/test_invoker.rb b/lib/ceedling/test_invoker.rb index 7dd7490b..aca86734 100644 --- a/lib/ceedling/test_invoker.rb +++ b/lib/ceedling/test_invoker.rb @@ -113,13 +113,19 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) # Fill out testables data structure with build context @batchinator.build_step("Ingesting Test Configurations") do + framework_defines = @helper.framework_defines() + runner_defines = @helper.runner_defines() + @batchinator.exec(workload: :compile, things: @testables) do |_, details| filepath = details[:filepath] search_paths = @helper.search_paths( filepath, details[:name] ) + compile_flags = @helper.flags( context:context, operation:OPERATION_COMPILE_SYM, filepath:filepath ) + preprocess_flags = @helper.preprocess_flags( context:context, compile_flags:compile_flags, filepath:filepath ) assembler_flags = @helper.flags( context:context, operation:OPERATION_ASSEMBLE_SYM, filepath:filepath ) link_flags = @helper.flags( context:context, operation:OPERATION_LINK_SYM, filepath:filepath ) + compile_defines = @helper.compile_defines( context:context, filepath:filepath ) preprocess_defines = @helper.preprocess_defines( test_defines: compile_defines, filepath:filepath ) @@ -132,11 +138,12 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) @lock.synchronize do details[:search_paths] = search_paths + details[:preprocess_flags] = preprocess_flags details[:compile_flags] = compile_flags details[:assembler_flags] = assembler_flags details[:link_flags] = link_flags - details[:compile_defines] = compile_defines - details[:preprocess_defines] = preprocess_defines + details[:compile_defines] = compile_defines + framework_defines + runner_defines + details[:preprocess_defines] = preprocess_defines + framework_defines end end end @@ -147,7 +154,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) arg_hash = { filepath: details[:filepath], test: details[:name], - flags: details[:compile_flags], + flags: details[:preprocess_flags], include_paths: details[:search_paths], defines: details[:preprocess_defines] } @@ -214,7 +221,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) arg_hash = { filepath: details[:source], test: testable[:name], - flags: testable[:compile_flags], + flags: testable[:preprocess_flags], include_paths: testable[:search_paths], defines: testable[:preprocess_defines] } @@ -248,7 +255,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) arg_hash = { filepath: details[:filepath], test: details[:name], - flags: details[:compile_flags], + flags: details[:preprocess_flags], include_paths: details[:search_paths], defines: details[:preprocess_defines] } diff --git a/lib/ceedling/test_invoker_helper.rb b/lib/ceedling/test_invoker_helper.rb index 051eeba5..37c0af3e 100644 --- a/lib/ceedling/test_invoker_helper.rb +++ b/lib/ceedling/test_invoker_helper.rb @@ -91,12 +91,8 @@ def search_paths(filepath, subdir) return paths.uniq end - def compile_defines(context:, filepath:) - # If this context exists ([:defines][context]), use it. Otherwise, default to test context. - context = TEST_SYM unless @defineinator.defines_defined?( context:context ) - - defines = @defineinator.generate_test_definition( filepath:filepath ) - defines += @defineinator.defines( subkey:context, filepath:filepath ) + def framework_defines() + defines = [] # Unity defines defines += @defineinator.defines( topkey:UNITY_SYM, subkey: :defines ) @@ -107,9 +103,6 @@ def compile_defines(context:, filepath:) # CException defines defines += @defineinator.defines( topkey:CEXCEPTION_SYM, subkey: :defines ) - # Injected defines (based on other settings) - defines += @test_runner_manager.collect_defines - return defines.uniq end @@ -152,19 +145,48 @@ def tailor_search_paths(filepath:, search_paths:) return _search_paths.uniq end + def runner_defines() + return @test_runner_manager.collect_defines() + end + + def compile_defines(context:, filepath:) + # If this context exists ([:defines][context]), use it. Otherwise, default to test context. + context = TEST_SYM unless @defineinator.defines_defined?( context:context ) + + defines = @defineinator.generate_test_definition( filepath:filepath ) + defines += @defineinator.defines( subkey:context, filepath:filepath ) + + return defines.uniq + end + def preprocess_defines(test_defines:, filepath:) # Preprocessing defines for the test file - preprocessing_defines = @defineinator.defines( subkey:PREPROCESS_SYM, filepath:filepath ) - - # If no preprocessing defines are present, default to the test compilation defines - return (preprocessing_defines.empty? ? test_defines : preprocessing_defines) + preprocessing_defines = @defineinator.defines( subkey:PREPROCESS_SYM, filepath:filepath, default:nil ) + + # If no defines were set, default to using test_defines + return test_defines if preprocessing_defines.nil? + + # Otherwise, return the defines we looked up + # This includes an explicitly set empty list to override / clear test_defines + return preprocessing_defines end - def flags(context:, operation:, filepath:) + def flags(context:, operation:, filepath:, default:[]) # If this context + operation exists ([:flags][context][operation]), use it. Otherwise, default to test context. context = TEST_SYM unless @flaginator.flags_defined?( context:context, operation:operation ) - return @flaginator.flag_down( context:context, operation:operation, filepath:filepath ) + return @flaginator.flag_down( context:context, operation:operation, filepath:filepath, default:default ) + end + + def preprocess_flags(context:, compile_flags:, filepath:) + preprocessing_flags = flags( context:context, operation:OPERATION_PREPROCESS_SYM, filepath:filepath, default:nil ) + + # If no flags were set, default to using compile_flags + return compile_flags if preprocessing_flags.nil? + + # Otherwise, return the flags we looked up + # This includes an explicitly set empty list to override / clear compile_flags + return preprocessing_flags end def collect_test_framework_sources(mocks)