From 7dc509dd2eecd5417cc0b24eb4fafc73cade3285 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 19 Jun 2024 13:52:47 -0400 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20More=20better=20config=20s?= =?UTF-8?q?et=20up=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implemented the logic and ordering of PR #818. To oversimplify, user configuration is assembled first and then all defaults are applied. A handful of methods have been broken into two renamed methods, one for default handling and another for user configuration handling. - Added lots of comments and updated `Configurator` method names to use _populate_, _merge_, etc. consistently. - Refactored `TestRunnerManager` to move configuration validation into Configurator & friends - Renamed Backtrace `gdb` tool definition to match other tools and added support for the tool argument shortcut in project configuration. --- lib/ceedling/configurator.rb | 173 ++++++++++-------- lib/ceedling/configurator_builder.rb | 13 +- lib/ceedling/configurator_setup.rb | 20 +- lib/ceedling/constants.rb | 2 + lib/ceedling/defaults.rb | 41 +++-- .../generator_test_results_backtrace.rb | 2 +- lib/ceedling/objects.yml | 2 - lib/ceedling/setupinator.rb | 153 +++++++++++----- lib/ceedling/test_runner_manager.rb | 58 ++---- 9 files changed, 283 insertions(+), 181 deletions(-) diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index d1eea979..ed6d38fa 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -59,7 +59,8 @@ def reset_defaults(config) :release_compiler, :release_assembler, :release_linker, - :release_dependencies_generator].each do |tool| + :release_dependencies_generator + ].each do |tool| config[:tools].delete(tool) if (not (config[:tools][tool].nil?)) end end @@ -69,8 +70,10 @@ def reset_defaults(config) # We do this because early config validation failures may need access to verbosity, # but the accessors won't be available until after configuration is validated. def set_verbosity(config) - # PROJECT_VERBOSITY and PROJECT_DEBUG were set at command line processing - # before Ceedling is even loaded. + # PROJECT_VERBOSITY and PROJECT_DEBUG set at command line processing before Ceedling is loaded + + # Configurator will later try to create these accessors automatically but will silently + # fail if they already exist. if (!!defined?(PROJECT_DEBUG) and PROJECT_DEBUG) or (config[:project][:debug]) eval("def project_debug() return true end", binding()) @@ -81,63 +84,99 @@ def set_verbosity(config) if !!defined?(PROJECT_VERBOSITY) eval("def project_verbosity() return #{PROJECT_VERBOSITY} end", binding()) end - - # Configurator will try to create these accessors automatically but will silently - # fail if they already exist. end # The default values defined in defaults.rb (eg. DEFAULT_TOOLS_TEST) are populated # into @param config - def populate_defaults(config) - new_config = DEFAULT_CEEDLING_CONFIG.deep_clone - new_config.deep_merge!(config) - config.replace(new_config) + def merge_tools_defaults(config, default_config) + default_config.deep_merge( DEFAULT_TOOLS_TEST.deep_clone() ) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST ) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST_PREPROCESSORS ) if (config[:project][:use_test_preprocessor]) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST_ASSEMBLER ) if (config[:test_build][:use_assembly]) + default_config.deep_merge( DEFAULT_TOOLS_TEST_PREPROCESSORS.deep_clone() ) if (config[:project][:use_test_preprocessor]) + default_config.deep_merge( DEFAULT_TOOLS_TEST_ASSEMBLER.deep_clone() ) if (config[:test_build][:use_assembly]) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE ) if (config[:project][:release_build]) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE_ASSEMBLER ) if (config[:project][:release_build] and config[:release_build][:use_assembly]) + default_config.deep_merge( DEFAULT_TOOLS_RELEASE.deep_clone() ) if (config[:project][:release_build]) + default_config.deep_merge( DEFAULT_TOOLS_RELEASE_ASSEMBLER.deep_clone() ) if (config[:project][:release_build] and config[:release_build][:use_assembly]) end - def populate_unity_defaults(config) - unity = config[:unity] || {} - - unity[:defines] = [] if (unity[:defines].nil?) - end - - - def populate_cmock_defaults(config) + def populate_cmock_defaults(config, default_config) # Cmock has its own internal defaults handling, but we need to set these specific values # so they're present for the build environment to access; - # Note: These need to end up in the hash given to initialize cmock for this to be successful - cmock = config[:cmock] || {} + # Note: these need to end up in the hash given to initialize cmock for this to be successful + + # Populate defaults with CMock internal settings + default_cmock = default_config[:cmock] || {} # Yes, we're duplicating the defaults in CMock, but it's because: # (A) We always need CMOCK_MOCK_PREFIX in Ceedling's environment # (B) Test runner generator uses these same configuration values - cmock[:mock_prefix] = 'Mock' if (cmock[:mock_prefix].nil?) - cmock[:mock_suffix] = '' if (cmock[:mock_suffix].nil?) + default_cmock[:mock_prefix] = 'Mock' if (default_cmock[:mock_prefix].nil?) + default_cmock[:mock_suffix] = '' if (default_cmock[:mock_suffix].nil?) + + # Just because strict ordering is the way to go + default_cmock[:enforce_strict_ordering] = true if (default_cmock[:enforce_strict_ordering].nil?) + + default_cmock[:mock_path] = File.join(config[:project][:build_root], TESTS_BASE_PATH, 'mocks') if (default_cmock[:mock_path].nil?) + + default_cmock[:verbosity] = project_verbosity() if (default_cmock[:verbosity].nil?) + end + + + def prepare_plugins_load_paths(plugins_load_path, config) + # Plugins must be loaded before generic path evaluation & magic that happen later. + # So, perform path magic here as discrete step. + config[:plugins][:load_paths].each do |path| + path.replace( @system_wrapper.module_eval( path ) ) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) + FilePathUtils::standardize( path ) + end + + # Add Ceedling's plugins path as load path so built-in plugins can be found + config[:plugins][:load_paths] << plugins_load_path + config[:plugins][:load_paths].uniq! + + return @configurator_plugins.process_aux_load_paths( config ) + end - # just because strict ordering is the way to go - cmock[:enforce_strict_ordering] = true if (cmock[:enforce_strict_ordering].nil?) - cmock[:mock_path] = File.join(config[:project][:build_root], TESTS_BASE_PATH, 'mocks') if (cmock[:mock_path].nil?) + def merge_plugins_defaults(paths_hash, config, default_config) + # Config YAML defaults plugins + plugin_yml_defaults = @configurator_plugins.find_plugin_yml_defaults( config, paths_hash ) + + # Config Ruby-based hash defaults plugins + plugin_hash_defaults = @configurator_plugins.find_plugin_hash_defaults( config, paths_hash ) - # Use dynamically defined accessor - cmock[:verbosity] = project_verbosity() if (cmock[:verbosity].nil?) + # Load base configuration values (defaults) from YAML + plugin_yml_defaults.each do |defaults| + default_config.deep_merge( @yaml_wrapper.load( defaults ) ) + end - cmock[:plugins] = [] if (cmock[:plugins].nil?) - cmock[:plugins].map! { |plugin| plugin.to_sym } + # Load base configuration values (defaults) as hash from Ruby + plugin_hash_defaults.each do |defaults| + default_config.deep_merge( defaults ) + end + end + + + def merge_ceedling_runtime_config(config, runtime_config) + # Merge Ceedling's internal runtime configuration settings + config.deep_merge( runtime_config ) + end + + + def populate_cmock_config(config) + # Populate config with CMock config + cmock = config[:cmock] || {} + + cmock[:plugins] = [] if (cmock[:plugins].nil?) + cmock[:plugins].map! { |plugin| plugin.to_sym() } cmock[:plugins].uniq! - cmock[:unity_helper] = false if (cmock[:unity_helper].nil?) + cmock[:unity_helper] = false if (cmock[:unity_helper].nil?) if (cmock[:unity_helper]) cmock[:unity_helper] = [cmock[:unity_helper]] if cmock[:unity_helper].is_a? String + cmock[:includes] = [] if (cmock[:includes].nil?) cmock[:includes] += cmock[:unity_helper].map{|helper| File.basename(helper) } cmock[:includes].uniq! end @@ -146,11 +185,11 @@ def populate_cmock_defaults(config) end - def configure_test_runner_generation(config) + def populate_test_runner_generation_config(config) use_backtrace = config[:project][:use_backtrace] - # TODO: Potentially update once :gdb and :simple are disentangled - if (use_backtrace == :gdb) or (use_backtrace == :simple) + # Force command line argument option for any backtrace option + if use_backtrace != :none config[:test_runner][:cmdline_args] = true end @@ -186,7 +225,7 @@ def get_cmock_config # - Handle inline Ruby string substitution # - Handle needed defaults # - Configure test runner from backtrace configuration - def tools_setup(config) + def populate_tools_config(config) config[:tools].each_key do |name| tool = config[:tools][name] @@ -211,7 +250,7 @@ def tools_setup(config) end - def tools_supplement_arguments(config) + def populate_tools_supplemental_arguments(config) tools_name_prefix = 'tools_' config[:tools].each_key do |name| tool = @project_config_hash[(tools_name_prefix + name.to_s).to_sym] @@ -223,50 +262,21 @@ def tools_supplement_arguments(config) if (not config[top_level_tool].nil?) # Adding and flattening is not a good idea -- might over-flatten if # there's array nesting in tool args. - tool[:arguments].concat config[top_level_tool][:arguments] + tool[:arguments].concat( config[top_level_tool][:arguments] ) end end end - def find_and_merge_plugins(plugins_load_path, config) - # Plugins must be loaded before generic path evaluation & magic that happen later. - # So, perform path magic here as discrete step. - config[:plugins][:load_paths].each do |path| - path.replace( @system_wrapper.module_eval(path) ) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) - FilePathUtils::standardize(path) - end - - # Add Ceedling's plugins path as load path so built-in plugins can be found - config[:plugins][:load_paths] << plugins_load_path - config[:plugins][:load_paths].uniq! - - paths_hash = @configurator_plugins.process_aux_load_paths(config) - + def merge_plugins_config(paths_hash, plugins_load_path, config) # Rake-based plugins @rake_plugins = @configurator_plugins.find_rake_plugins( config, paths_hash ) - # Ruby `PLugin` subclass programmatic plugins + # Ruby `Plugin` subclass programmatic plugins @programmatic_plugins = @configurator_plugins.find_programmatic_plugins( config, paths_hash ) - # Config YAML defaults plugins - plugin_yml_defaults = @configurator_plugins.find_plugin_yml_defaults( config, paths_hash ) - - # Config Ruby-based hash defaults plugins - plugin_hash_defaults = @configurator_plugins.find_plugin_hash_defaults( config, paths_hash ) - # Config plugins - config_plugins = @configurator_plugins.find_config_plugins( config, paths_hash ) - - # Load base configuration values (defaults) from YAML - plugin_yml_defaults.each do |defaults| - @configurator_builder.populate_defaults( config, @yaml_wrapper.load(defaults) ) - end - - # Load base configuration values (defaults) as hash from Ruby - plugin_hash_defaults.each do |defaults| - @configurator_builder.populate_defaults( config, defaults ) - end + config_plugins = @configurator_plugins.find_config_plugins( config, paths_hash ) # Merge plugin configuration values (like Ceedling project file) config_plugins.each do |plugin| @@ -274,9 +284,9 @@ def find_and_merge_plugins(plugins_load_path, config) # Special handling for plugin paths if (plugin_config.include?( :paths )) - plugin_config[:paths].update(plugin_config[:paths]) do |k,v| - plugin_path = plugin.match(/(.*)[\/]config[\/]\w+\.yml/)[1] - v.map {|vv| File.expand_path(vv.gsub!(/\$PLUGIN_PATH/,plugin_path)) } + plugin_config[:paths].update( plugin_config[:paths] ) do |k,v| + plugin_path = plugin.match( /(.*)[\/]config[\/]\w+\.yml/ )[1] + v.map {|vv| File.expand_path( vv.gsub!( /\$PLUGIN_PATH/, plugin_path) ) } end end @@ -292,8 +302,10 @@ def find_and_merge_plugins(plugins_load_path, config) # Process environment variables set in configuration file - # (Each entry beneath :environment is another hash) + # (Each entry within the :environment array is a hash) def eval_environment_variables(config) + return if config[:environment].nil? + config[:environment].each do |hash| key = hash.keys[0] # Get first (should be only) environment variable entry value = hash[key] # Get associated value @@ -406,11 +418,16 @@ def validate_essential(config) end - def validate_final(config) + def validate_final(config, app_cfg) # Collect all infractions, everybody on probation until final adjudication blotter = true blotter &= @configurator_setup.validate_paths( config ) blotter &= @configurator_setup.validate_tools( config ) + blotter &= @configurator_setup.validate_test_runner_generation( + config, + app_cfg[:include_test_case], + app_cfg[:exclude_test_case] + ) blotter &= @configurator_setup.validate_backtrace( config ) blotter &= @configurator_setup.validate_threads( config ) blotter &= @configurator_setup.validate_plugins( config ) diff --git a/lib/ceedling/configurator_builder.rb b/lib/ceedling/configurator_builder.rb index a873d0ad..af2ef49b 100644 --- a/lib/ceedling/configurator_builder.rb +++ b/lib/ceedling/configurator_builder.rb @@ -85,11 +85,18 @@ def flattenify(config) end + # If config lacks an entry that defaults posseses, add a config entry with the default value def populate_defaults(config, defaults) defaults.keys.sort.each do |section| - defaults[section].keys.sort.each do |entry| - config[section] = {} if config[section].nil? - config[section][entry] = defaults[section][entry].deep_clone if (config[section][entry].nil?) + case defaults[section] + when Hash + defaults[section].keys.sort.each do |entry| + config[section] = {} if config[section].nil? + config[section][entry] = defaults[section][entry].deep_clone if (config[section][entry].nil?) + end + + when Array + config[section] = defaults[section] end end end diff --git a/lib/ceedling/configurator_setup.rb b/lib/ceedling/configurator_setup.rb index 92d84d7f..7ed7ef9f 100644 --- a/lib/ceedling/configurator_setup.rb +++ b/lib/ceedling/configurator_setup.rb @@ -164,7 +164,7 @@ def validate_tools(config) if config[:project][:use_backtrace] == :gdb valid &= @configurator_validator.validate_tool( config:config, - key: :backtrace_reporter, + key: :test_backtrace_gdb, respect_optional: false ) end @@ -172,6 +172,22 @@ def validate_tools(config) return valid end + def validate_test_runner_generation(config, include_test_case, exclude_test_case) + cmdline_args = config[:test_runner][:cmdline_args] + + # Test case filters in use + test_case_filters = !include_test_case.empty? || !exclude_test_case.empty? + + # Test case filters are in use but test runner command line arguments are not enabled + if (test_case_filters and !cmdline_args) + msg = 'Test case filters cannot be used -- enable :test_runner ↳ :cmdline_args in your project configuration' + @loginator.log( msg, Verbosity::ERRORS ) + return false + end + + return true + end + def validate_backtrace(config) valid = true @@ -185,7 +201,7 @@ def validate_backtrace(config) when :gdb # Do nothing else - @loginator.log( ":project ↳ :use_backtrace is '#{use_backtrace}' but must be :none, :simple, or :gdb", Verbosity::ERRORS ) + @loginator.log( ":project ↳ :use_backtrace is '#{use_backtrace}' but must be :none, :simple, or :gdb", Verbosity::ERRORS ) valid = false end diff --git a/lib/ceedling/constants.rb b/lib/ceedling/constants.rb index 2e1fef56..8a4813a8 100644 --- a/lib/ceedling/constants.rb +++ b/lib/ceedling/constants.rb @@ -96,6 +96,8 @@ class StdErrRedirect UNITY_TEST_SOURCE_FILE = 'TEST_SOURCE_FILE' UNITY_TEST_INCLUDE_PATH = 'TEST_INCLUDE_PATH' +RUNNER_BUILD_CMDLINE_ARGS_DEFINE = 'UNITY_USE_COMMAND_LINE_ARGS' + CMOCK_SYM = :cmock CMOCK_ROOT_PATH = 'cmock' CMOCK_LIB_PATH = "#{CMOCK_ROOT_PATH}/src" diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index 115d2bcd..c82681d2 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -231,9 +231,9 @@ ].freeze } -DEFAULT_BACKTRACE_TOOL = { +DEFAULT_TEST_BACKTRACE_GDB_TOOL = { :executable => ENV['GDB'].nil? ? FilePathUtils.os_executable_ext('gdb').freeze : ENV['GDB'], - :name => 'default_backtrace_reporter'.freeze, + :name => 'default_test_backtrace_gdb'.freeze, # Must be optional because validation is contingent on backtrace configuration. # (Don't break a build if `gdb` is unavailable but backtrace does not require it.) :optional => true.freeze, @@ -254,7 +254,7 @@ :test_linker => DEFAULT_TEST_LINKER_TOOL, :test_fixture => DEFAULT_TEST_FIXTURE_TOOL, :test_fixture_simple_backtrace => DEFAULT_TEST_FIXTURE_SIMPLE_BACKTRACE_TOOL, - :backtrace_reporter => DEFAULT_BACKTRACE_TOOL, + :test_backtrace_gdb => DEFAULT_TEST_BACKTRACE_GDB_TOOL, } } @@ -300,7 +300,7 @@ DEFAULT_RELEASE_TARGET_NAME = 'project' -DEFAULT_CEEDLING_CONFIG = { +DEFAULT_CEEDLING_PROJECT_CONFIG = { :project => { # :build_root must be set by user :use_mocks => true, @@ -323,6 +323,9 @@ :use_assembly => false }, + # Unlike other top-level entries, :environment is an array (of hashes) to preserve order + :environment => [], + :paths => { :test => [], # Must be populated by user :source => [], # Should be populated by user but TEST_INCLUDE_PATH() could be used exclusively instead @@ -341,9 +344,6 @@ :include => [], }, - # unlike other top-level entries, environment's value is an array to preserve order - :environment => [], - :defines => { :use_test_definition => false, :test => [], # A hash/sub-hashes in config file can include operations and test executable matchers as keys @@ -380,18 +380,15 @@ }, :unity => { - :vendor_path => CEEDLING_VENDOR, :defines => [] }, :cmock => { - :vendor_path => CEEDLING_VENDOR, :includes => [], :defines => [] }, :cexception => { - :vendor_path => CEEDLING_VENDOR, :defines => [] }, @@ -402,11 +399,11 @@ :file_suffix => '_runner', }, - # all tools populated while building up config structure + # All tools populated while building up config / defaults structure :tools => {}, - # empty argument lists for default tools - # (these can be overridden in project file to add arguments to tools without totally redefining tools) + # Empty argument lists for default tools + # Note: These can be overridden in project file to add arguments totally redefining tools :test_compiler => { :arguments => [] }, :test_assembler => { :arguments => [] }, :test_linker => { :arguments => [] }, @@ -414,6 +411,7 @@ :arguments => [], :link_objects => [], # compiled object files to always be linked in (e.g. cmock.o if using mocks) }, + :test_backtrace_gdb => { :arguments => [] }, :test_includes_preprocessor => { :arguments => [] }, :test_file_preprocessor => { :arguments => [] }, :test_file_preprocessor_directives => { :arguments => [] }, @@ -421,7 +419,22 @@ :release_compiler => { :arguments => [] }, :release_linker => { :arguments => [] }, :release_assembler => { :arguments => [] }, - :release_dependencies_generator => { :arguments => [] }, + :release_dependencies_generator => { :arguments => [] } + }.freeze + + +CEEDLING_RUNTIME_CONFIG = { + :unity => { + :vendor_path => CEEDLING_VENDOR + }, + + :cmock => { + :vendor_path => CEEDLING_VENDOR + }, + + :cexception => { + :vendor_path => CEEDLING_VENDOR + }, :plugins => { :load_paths => [], diff --git a/lib/ceedling/generator_test_results_backtrace.rb b/lib/ceedling/generator_test_results_backtrace.rb index 98cf6388..a83b1bc0 100644 --- a/lib/ceedling/generator_test_results_backtrace.rb +++ b/lib/ceedling/generator_test_results_backtrace.rb @@ -88,7 +88,7 @@ def do_gdb(filename, executable, shell_result, test_cases) test_cases.each do |test_case| # Build the test fixture to run with our test case of interest command = @tool_executor.build_command_line( - @configurator.tools_backtrace_reporter, [], + @configurator.tools_test_backtrace_gdb, [], gdb_script_filepath, executable, test_case[:test] diff --git a/lib/ceedling/objects.yml b/lib/ceedling/objects.yml index 04e4e3f7..691dc0f6 100644 --- a/lib/ceedling/objects.yml +++ b/lib/ceedling/objects.yml @@ -41,8 +41,6 @@ file_path_collection_utils: - file_wrapper test_runner_manager: - compose: - - configurator cacheinator: compose: diff --git a/lib/ceedling/setupinator.rb b/lib/ceedling/setupinator.rb index fd1fe4ae..0ad5125a 100644 --- a/lib/ceedling/setupinator.rb +++ b/lib/ceedling/setupinator.rb @@ -8,10 +8,8 @@ class Setupinator attr_reader :config_hash - attr_writer :ceedling def setup - @ceedling = {} @config_hash = {} end @@ -24,56 +22,129 @@ def inspect end + def ceedling=(value) + # Application objects hash + @ceedling = value + + # References for brevity + @configurator = value[:configurator] + @loginator = value[:loginator] + @configurator_builder = value[:configurator_builder] + @plugin_manager = value[:plugin_manager] + @plugin_reportinator = value[:plugin_reportinator] + @test_runner_manager = value[:test_runner_manager] + end + + + # Load up all the constants and accessors our rake files, objects, & external scripts will need. def do_setup( app_cfg ) @config_hash = app_cfg[:project_config] - log_filepath = app_cfg[:log_filepath] - - @ceedling[:configurator].include_test_case = app_cfg[:include_test_case] - @ceedling[:configurator].exclude_test_case = app_cfg[:exclude_test_case] - - # Load up all the constants and accessors our rake files, objects, & external scripts will need. - # Note: Configurator modifies the cmock section of the hash with a couple defaults to tie - # projects together -- the modified hash is used to build the cmock object. - @ceedling[:configurator].set_verbosity( config_hash ) - @ceedling[:configurator].validate_essential( config_hash ) - @ceedling[:configurator].populate_defaults( config_hash ) - @ceedling[:configurator].populate_unity_defaults( config_hash ) - @ceedling[:configurator].populate_cmock_defaults( config_hash ) - @ceedling[:configurator].configure_test_runner_generation( config_hash ) - # Evaluate environment vars before sections that might reference them with inline Ruby string expansion - @ceedling[:configurator].eval_environment_variables( config_hash ) - @ceedling[:configurator].eval_paths( config_hash ) - @ceedling[:configurator].eval_flags( config_hash ) - @ceedling[:configurator].eval_defines( config_hash ) - @ceedling[:configurator].standardize_paths( config_hash ) - @ceedling[:configurator].find_and_merge_plugins( app_cfg[:ceedling_plugins_path], config_hash ) - @ceedling[:configurator].tools_setup( config_hash ) - @ceedling[:configurator].validate_final( config_hash ) + + ## + ## 1. Miscellaneous handling and essential configuration prep + ## + + # Set special purpose test case filters (from command line) + @configurator.include_test_case = app_cfg[:include_test_case] + @configurator.exclude_test_case = app_cfg[:exclude_test_case] + + # Verbosity handling + @configurator.set_verbosity( config_hash ) + + # Logging configuration + @loginator.set_logfile( form_log_filepath( app_cfg[:log_filepath] ) ) + @configurator.project_logging = @loginator.project_logging + + # Complain early about anything essential that's missing + @configurator.validate_essential( config_hash ) + + # Merge any needed runtime settings into user configuration + @configurator.merge_ceedling_runtime_config( config_hash, CEEDLING_RUNTIME_CONFIG.deep_clone ) + + ## + ## 2. Handle core user configuration + ## + + # Evaluate environment vars before plugin configurations that might reference with inline Ruby string expansion + @configurator.eval_environment_variables( config_hash ) + + # Standardize paths and add to Ruby load paths + plugins_paths_hash = @configurator.prepare_plugins_load_paths( app_cfg[:ceedling_plugins_path], config_hash ) + + # Populate CMock configuration with values to tie vendor tool configurations together + @configurator.populate_cmock_config( config_hash ) + + @configurator.merge_plugins_config( plugins_paths_hash, app_cfg[:ceedling_plugins_path], config_hash ) + + ## + ## 3. Collect and apply defaults to user configuration + ## + + # Assemble defaults + defaults_hash = DEFAULT_CEEDLING_PROJECT_CONFIG.deep_clone() + @configurator.merge_tools_defaults( config_hash, defaults_hash ) + @configurator.populate_cmock_defaults( config_hash, defaults_hash ) + @configurator.merge_plugins_defaults( plugins_paths_hash, config_hash, defaults_hash ) + + # Set any essential missing or plugin values in configuration with assembled default values + @configurator_builder.populate_defaults( config_hash, defaults_hash ) + + ## + ## 4. Fill out / modify remaining configuration from user configuration + defaults + ## + + # Configure test runner generation + @configurator.populate_test_runner_generation_config( config_hash ) + + # Evaluate environment vars again before subsequent configurations that might reference with inline Ruby string expansion + @configurator.eval_environment_variables( config_hash ) + + # Standardize values and expand inline Ruby string substitutions + @configurator.eval_paths( config_hash ) + @configurator.eval_flags( config_hash ) + @configurator.eval_defines( config_hash ) + @configurator.standardize_paths( config_hash ) + + # Fill out any missing tool config value / supplement arguments + @configurator.populate_tools_config( config_hash ) + @configurator.populate_tools_supplemental_arguments( config_hash ) + + # Configure test runner build & runtime options + @test_runner_manager.configure_build_options( config_hash ) + @test_runner_manager.configure_runtime_options( app_cfg[:include_test_case], app_cfg[:exclude_test_case] ) + + ## + ## 5. Validate configuration + ## + + @configurator.validate_final( config_hash, app_cfg ) + + ## + ## 6. Flatten configuration + process it into globals and accessors + ## + # Partially flatten config + build Configurator accessors and globals - @ceedling[:configurator].build( app_cfg[:ceedling_lib_path], config_hash, :environment ) + @configurator.build( app_cfg[:ceedling_lib_path], config_hash, :environment ) - @ceedling[:configurator].insert_rake_plugins( @ceedling[:configurator].rake_plugins ) - @ceedling[:configurator].tools_supplement_arguments( config_hash ) + ## + ## 7. Final plugins handling + ## + + @configurator.insert_rake_plugins( @configurator.rake_plugins ) # Merge in any environment variables that plugins specify after the main build - @ceedling[:plugin_manager].load_programmatic_plugins( @ceedling[:configurator].programmatic_plugins, @ceedling ) do |env| - @ceedling[:configurator].eval_environment_variables( env ) - @ceedling[:configurator].build_supplement( config_hash, env ) + @plugin_manager.load_programmatic_plugins( @configurator.programmatic_plugins, @ceedling ) do |env| + # Evaluate environment vars that plugins may have added + @configurator.eval_environment_variables( env ) + @configurator.build_supplement( config_hash, env ) end # Inject dependencies for plugin needs - @ceedling[:plugin_reportinator].set_system_objects( @ceedling ) - - # Process options for additional test runner #defines and test runner command line arguments - @ceedling[:test_runner_manager].validate_and_configure_options() - - # Logging set up - @ceedling[:loginator].set_logfile( form_log_filepath( log_filepath ) ) - @ceedling[:configurator].project_logging = @ceedling[:loginator].project_logging + @plugin_reportinator.set_system_objects( @ceedling ) end def reset_defaults(config_hash) - @ceedling[:configurator].reset_defaults( config_hash ) + @configurator.reset_defaults( config_hash ) end ### Private @@ -86,7 +157,7 @@ def form_log_filepath( log_filepath ) # If there's no directory path, put named log file in default location if File.dirname( log_filepath ).empty?() - return File.join( @ceedling[:configurator].project_log_path, log_filepath ) + return File.join( @configurator.project_log_path, log_filepath ) end # Otherwise, log filepath includes a directory (that's already been created) diff --git a/lib/ceedling/test_runner_manager.rb b/lib/ceedling/test_runner_manager.rb index af7c0be8..47c2ee35 100644 --- a/lib/ceedling/test_runner_manager.rb +++ b/lib/ceedling/test_runner_manager.rb @@ -5,65 +5,43 @@ # SPDX-License-Identifier: MIT # ========================================================================= -require 'ceedling/exceptions' +require 'ceedling/constants' class TestRunnerManager - constructor :configurator - - def setup + def initialize() @test_case_incl = nil @test_case_excl = nil @test_runner_defines = [] end - # Return test case arguments (empty if not set) - def collect_cmdline_args() - return [ @test_case_incl, @test_case_excl ].compact() - end + def configure_build_options(config) + cmdline_args = config[:test_runner][:cmdline_args] - def validate_and_configure_options() - # Blow up immediately if things aren't right - return if !validated_and_configured?() + # Should never happen because of external config handling, but... + return if cmdline_args.nil? - @test_runner_defines << 'UNITY_USE_COMMAND_LINE_ARGS' + @test_runner_defines << RUNNER_BUILD_CMDLINE_ARGS_DEFINE if cmdline_args + end - if !@configurator.include_test_case.empty? - @test_case_incl = "-f #{@configurator.include_test_case}" + def configure_runtime_options(include_test_case, exclude_test_case) + if !include_test_case.empty? + @test_case_incl = "-f #{include_test_case}" end - if !@configurator.exclude_test_case.empty? - @test_case_excl = "-x #{@configurator.exclude_test_case}" + if !exclude_test_case.empty? + @test_case_excl = "-x #{exclude_test_case}" end end + # Return test case arguments (empty if not set) + def collect_cmdline_args() + return [ @test_case_incl, @test_case_excl ].compact() + end + # Return ['UNITY_USE_COMMAND_LINE_ARGS'] #define required by Unity to enable cmd line arguments def collect_defines() return @test_runner_defines end - ### Private ### - - private - - # Raise exception if lacking support for test case matching - def validated_and_configured?() - # Command line arguments configured - cmdline_args = @configurator.test_runner_cmdline_args - - # Test case filters in use - test_case_filters = (!@configurator.include_test_case.nil? && !@configurator.include_test_case.empty?) || - (!@configurator.exclude_test_case.nil? && !@configurator.exclude_test_case.empty?) - - # Test case filters are in use but test runner command line arguments are not enabled - if test_case_filters and !cmdline_args - # Blow up if filters are in use but test runner command line arguments are not enabled - msg = 'Unity test case filters cannot be used as configured. ' + - 'Enable :test_runner ↳ :cmdline_args in your project configuration.' - - raise CeedlingException.new( msg ) - end - - return cmdline_args - end end