Skip to content

Commit

Permalink
🐛 Extension handling now allows dotted filenames
Browse files Browse the repository at this point in the history
Dotted filenames are legal. Ceedling previously made too many assumptions about periods only being used for file extensions. For example, a legacy header file foo.33.h would break mocking. This is fixed.
  • Loading branch information
mkarlesky committed Oct 25, 2024
1 parent 6ed5209 commit af4f1ad
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 32 deletions.
45 changes: 30 additions & 15 deletions lib/ceedling/file_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,34 @@ class FileFinder
constructor :configurator, :file_finder_helper, :cacheinator, :file_path_utils, :file_wrapper, :yaml_wrapper


def find_header_file(mock_file)
header = File.basename(mock_file).sub(/#{@configurator.cmock_mock_prefix}/, '').ext(@configurator.extension_header)
def find_header_input_for_mock(mock_name)
# Mock name => <mock prefix><header filename without extension>
# Examples: 'Mockfoo' or 'mock_Bar'
# Note: In some rare cases, a mock name may include a dot (ex. Sensor.44) because of versioning file naming convention
# Be careful about assuming the end of the name has any sort of file extension

found_path = @file_finder_helper.find_file_in_collection(header, @configurator.collection_all_headers, :error, mock_file)
header = mock_name.sub(/#{@configurator.cmock_mock_prefix}/, '') + @configurator.extension_header

found_path = @file_finder_helper.find_file_in_collection(header, @configurator.collection_all_headers, :error, mock_name)

return found_path
end


def find_header_input_for_mock_file(mock_file)
return find_header_file(mock_file)
# Find test filepath from another filepath (e.g. test executable with same base name, a/path/test_foo.exe)
def find_test_file_from_filepath(filepath)
# Strip filepath down to filename and remove file extension
name = File.basename( filepath ).ext('')

return find_test_file_from_name( name )
end


def find_test_from_file_path(filepath)
test_file = File.basename(filepath).ext(@configurator.extension_source)
# Find test filepath from only the base name of a test file (e.g. 'test_foo')
def find_test_file_from_name(name)
test_file = name + @configurator.extension_source

found_path = @file_finder_helper.find_file_in_collection(test_file, @configurator.collection_all_tests, :error, filepath)
found_path = @file_finder_helper.find_file_in_collection(test_file, @configurator.collection_all_tests, :error, name)

return found_path
end
Expand All @@ -42,16 +52,21 @@ def find_build_input_file(filepath:, complain: :error, context:)

found_file = nil

# Strip off file extension
source_file = File.basename(filepath).ext('')

# We only collect files that already exist when we start up.
# FileLists can produce undesired results for dynamically generated files depending on when they're accessed.
# So collect mocks and runners separately and right now.
# Assume that project configuration options will have already filtered out any files that should not be searched for.

# Note: We carefully add file extensions below with string addition instead of using .ext()
# Some legacy files can include naming conventions like <name>.##.<ext> for versioning.
# If we use .ext() below we'll clobber the dotted portion of the filename

# Generated test runners
if (!release) and (source_file =~ /^#{@configurator.project_test_file_prefix}.+#{@configurator.test_runner_file_suffix}$/)
_source_file = source_file.ext(EXTENSION_CORE_SOURCE)
_source_file = source_file + EXTENSION_CORE_SOURCE
found_file =
@file_finder_helper.find_file_in_collection(
_source_file,
Expand All @@ -61,7 +76,7 @@ def find_build_input_file(filepath:, complain: :error, context:)

# Generated mocks
elsif (!release) and (source_file =~ /^#{@configurator.cmock_mock_prefix}/)
_source_file = source_file.ext(EXTENSION_CORE_SOURCE)
_source_file = source_file + EXTENSION_CORE_SOURCE
found_file =
@file_finder_helper.find_file_in_collection(
_source_file,
Expand All @@ -72,7 +87,7 @@ def find_build_input_file(filepath:, complain: :error, context:)
# Vendor framework sources (unity.c, cmock.c, cexception.c, etc.)
# Note: Taking a small chance by mixing test and release frameworks without smart checks on test/release build
elsif (@configurator.collection_vendor_framework_sources.include?(source_file.ext(EXTENSION_CORE_SOURCE)))
_source_file = source_file.ext(EXTENSION_CORE_SOURCE)
_source_file = source_file + EXTENSION_CORE_SOURCE
found_file =
@file_finder_helper.find_file_in_collection(
_source_file,
Expand All @@ -97,7 +112,7 @@ def find_build_input_file(filepath:, complain: :error, context:)

# Assembly files for release build
if release and @configurator.release_build_use_assembly
_source_file = File.basename(filepath).ext(@configurator.extension_assembly)
_source_file = source_file + @configurator.extension_assembly
found_file =
@file_finder_helper.find_file_in_collection(
_source_file,
Expand All @@ -107,7 +122,7 @@ def find_build_input_file(filepath:, complain: :error, context:)

# Assembly files for test build
elsif (!release) and @configurator.test_build_use_assembly
_source_file = File.basename(filepath).ext(@configurator.extension_assembly)
_source_file = source_file + @configurator.extension_assembly
found_file =
@file_finder_helper.find_file_in_collection(
_source_file,
Expand All @@ -122,7 +137,7 @@ def find_build_input_file(filepath:, complain: :error, context:)

# Release build C files
if release
_source_file = File.basename(filepath).ext(@configurator.extension_source)
_source_file = source_file + @configurator.extension_source
found_file =
@file_finder_helper.find_file_in_collection(
_source_file,
Expand All @@ -132,7 +147,7 @@ def find_build_input_file(filepath:, complain: :error, context:)

# Test build C files
else
_source_file = File.basename(filepath).ext(@configurator.extension_source)
_source_file = source_file + @configurator.extension_source
found_file =
@file_finder_helper.find_file_in_collection(
_source_file,
Expand Down
2 changes: 1 addition & 1 deletion lib/ceedling/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def generate_test_results(tool:, context:, test_name:, test_filepath:, executabl
executable,
shell_result,
arg_hash[:result_file],
@file_finder.find_test_from_file_path( arg_hash[:executable] )
@file_finder.find_test_file_from_filepath( arg_hash[:executable] )
)

arg_hash[:result_file] = processed[:result_file]
Expand Down
6 changes: 3 additions & 3 deletions lib/ceedling/rules_tests.rake
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ namespace TEST_SYM do
:test_fixture => TOOLS_TEST_FIXTURE
}

# use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks)
rule(/^#{TEST_TASK_ROOT}\S+$/ => [ # test task names by regex
# Use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks)
rule(/^#{TEST_TASK_ROOT}\S+$/ => [ # Test task names by regex
proc do |task_name|
test = task_name.sub(/#{TEST_TASK_ROOT}/, '')
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" if not (test.start_with?(PROJECT_TEST_FILE_PREFIX))
@ceedling[:file_finder].find_test_from_file_path(test)
@ceedling[:file_finder].find_test_file_from_name(test)
end
]) do |test|
@ceedling[:rake_wrapper][:prepare].invoke
Expand Down
15 changes: 6 additions & 9 deletions lib/ceedling/test_invoker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{})
mocks = {}
mocks_list = @configurator.project_use_mocks ? @context_extractor.lookup_raw_mock_list( details[:filepath] ) : []
mocks_list.each do |name|
source = @helper.find_header_input_for_mock_file( name, details[:search_paths] )
source = @helper.find_header_input_for_mock( name, details[:search_paths] )
preprocessed_input = @file_path_utils.form_preprocessed_file_filepath( source, details[:name] )
mocks[name.to_sym] = {
:name => name,
Expand Down Expand Up @@ -303,14 +303,11 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{})
@batchinator.build_step("Determining Artifacts to Be Built", heading: false) do
@batchinator.exec(workload: :compile, things: @testables) do |test, details|
# Source files referenced by conventions or specified by build directives in a test file
test_sources = @test_invoker_helper.extract_sources( details[:filepath] )
test_core = test_sources + details[:mock_list]
test_sources = @helper.extract_sources( details[:filepath] )
test_core = test_sources + @helper.form_mock_filenames( details[:mock_list] )

# When we have a mock and an include for the same file, the mock wins
test_core.delete_if do |v|
mock_of_this_file = "#{@configurator.cmock_mock_prefix}#{File.basename(v,'.*')}"
details[:mock_list].include?(mock_of_this_file)
end
@helper.remove_mock_original_headers( test_core, details[:mock_list] )

# CMock + Unity + CException
test_frameworks = @helper.collect_test_framework_sources( !details[:mock_list].empty? )
Expand Down Expand Up @@ -380,7 +377,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{})
options: options
}

@test_invoker_helper.generate_executable_now(**arg_hash)
@helper.generate_executable_now(**arg_hash)
end
end

Expand All @@ -397,7 +394,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{})
options: options
}

@test_invoker_helper.run_fixture_now(**arg_hash)
@helper.run_fixture_now(**arg_hash)

# Handle exceptions so we can ensure post_test() is called.
# A lone `ensure` includes an implicit rescuing of StandardError
Expand Down
19 changes: 17 additions & 2 deletions lib/ceedling/test_invoker_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,23 @@ def fetch_include_search_paths_for_test_file(test_filepath)
end

# TODO: Use search_paths to find/match header file from which to generate mock
def find_header_input_for_mock_file(mock, search_paths)
return @file_finder.find_header_input_for_mock_file(mock)
# Today, this is just a pass-through wrapper
def find_header_input_for_mock(mock, search_paths)
return @file_finder.find_header_input_for_mock( mock )
end

# Transform list of mock names into filenames with source extension
def form_mock_filenames(mocklist)
return mocklist.map {|mock| mock + @configurator.extension_source}
end

def remove_mock_original_headers( filelist, mocklist )
filelist.delete_if do |filepath|
# Create a simple mock name from the filepath => mock prefix + filepath base name with no extension
mock_name = @configurator.cmock_mock_prefix + File.basename( filepath, '.*' )
# Tell `delete_if()` logic to remove inspected filepath if simple mocklist includes the name we just generated
mocklist.include?( mock_name )
end
end

def clean_test_results(path, tests)
Expand Down
2 changes: 1 addition & 1 deletion plugins/bullseye/bullseye.rake
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ namespace BULLSEYE_SYM do
proc do |task_name|
test = task_name.sub(/#{BULLSEYE_TASK_ROOT}/, '')
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" unless test.start_with?(PROJECT_TEST_FILE_PREFIX)
@ceedling[:file_finder].find_test_from_file_path(test)
@ceedling[:file_finder].find_test_file_from_name(test)
end
]) do |test|
@ceedling[:rake_wrapper][:prepare].invoke
Expand Down
2 changes: 1 addition & 1 deletion plugins/gcov/gcov.rake
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ namespace GCOV_SYM do
proc do |task_name|
test = task_name.sub(/#{GCOV_TASK_ROOT}/, '')
test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" unless test.start_with?(PROJECT_TEST_FILE_PREFIX)
@ceedling[:file_finder].find_test_from_file_path(test)
@ceedling[:file_finder].find_test_file_from_name(test)
end
]) do |test|
@ceedling[:rake_wrapper][:prepare].invoke
Expand Down

0 comments on commit af4f1ad

Please sign in to comment.