Skip to content

Commit

Permalink
♻️ More better gdb execution
Browse files Browse the repository at this point in the history
Used elements of PR #856 to robustify gdb execution

- Placed gdb commands in a file
- gdb has more resilence in relation to process exit and suppresses warnings
- Tidied up the tool definitions with comments and `freeze()` calls
  • Loading branch information
mkarlesky committed Jun 13, 2024
1 parent 3ee047b commit cc0ad41
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 23 deletions.
5 changes: 5 additions & 0 deletions lib/ceedling/backtrace.gdb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if $_isvoid ($_exitcode)
call ((void(*)(int))fflush)(0)
backtrace
kill
end
6 changes: 3 additions & 3 deletions lib/ceedling/configurator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -422,15 +422,15 @@ def validate_final(config)


# Create constants and accessors (attached to this object) from given hash
def build(build_project_config, config, *keys)
def build(ceedling_lib_path, config, *keys)
flattened_config = @configurator_builder.flattenify( config )

@configurator_setup.build_project_config( build_project_config, flattened_config )
@configurator_setup.build_project_config( ceedling_lib_path, flattened_config )

@configurator_setup.build_directory_structure( flattened_config )

# Copy Unity, CMock, CException into vendor directory within build directory
@configurator_setup.vendor_frameworks( flattened_config )
@configurator_setup.vendor_frameworks_and_support_files( ceedling_lib_path, flattened_config )

@configurator_setup.build_project_collections( flattened_config )

Expand Down
2 changes: 1 addition & 1 deletion lib/ceedling/configurator_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def set_build_paths(in_hash)
[:project_release_build_output_path, File.join(project_build_release_root, 'out'), in_hash[:project_release_build] ],
[:project_release_dependencies_path, File.join(project_build_release_root, 'dependencies'), in_hash[:project_release_build] ],

[:project_log_path, File.join(in_hash[:project_build_root], 'logs'), true ],
[:project_log_path, File.join(in_hash[:project_build_root], 'logs'), true ],

[:project_test_preprocess_includes_path, File.join(project_build_tests_root, 'preprocess/includes'), in_hash[:project_use_test_preprocessor] ],
[:project_test_preprocess_files_path, File.join(project_build_tests_root, 'preprocess/files'), in_hash[:project_use_test_preprocessor] ],
Expand Down
10 changes: 8 additions & 2 deletions lib/ceedling/configurator_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def build_directory_structure(flattened_config)
end
end

def vendor_frameworks(flattened_config)
def vendor_frameworks_and_support_files(ceedling_lib_path, flattened_config)
# Copy Unity C files into build/vendor directory structure
@file_wrapper.cp_r(
# '/.' to cause cp_r to copy directory contents
Expand All @@ -70,10 +70,16 @@ def vendor_frameworks(flattened_config)
File.join( flattened_config[:cexception_vendor_path], CEXCEPTION_LIB_PATH, '/.' ),
flattened_config[:project_build_vendor_cexception_path]
) if flattened_config[:project_use_exceptions]

# Copy backtrace debugging script into build/test directory structure
@file_wrapper.cp_r(
File.join( ceedling_lib_path, BACKTRACE_GDB_SCRIPT_FILE ),
flattened_config[:project_build_tests_root]
) if flattened_config[:project_use_backtrace] == :gdb
end

def build_project_collections(flattened_config)
### iterate through all entries in paths section and expand any & all globs to actual paths
# Iterate through all entries in paths section and expand any & all globs to actual paths
flattened_config.merge!( @configurator_builder.expand_all_path_globs( flattened_config ) )

flattened_config.merge!( @configurator_builder.collect_vendor_paths( flattened_config ) )
Expand Down
5 changes: 5 additions & 0 deletions lib/ceedling/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class StdErrRedirect
TCSH = :tcsh
end

# Escaped newline literal (literally double-slash-n) for "encoding" multiline strings as single string
NEWLINE_TOKEN = '\\n'

DEFAULT_PROJECT_FILENAME = 'project.yml'

GENERATED_DIR_PATH = [['vendor', 'ceedling'], 'src', "test", ['test', 'support'], 'build'].each{|p| File.join(*p)}
Expand Down Expand Up @@ -101,6 +104,8 @@ class StdErrRedirect

DEFAULT_CEEDLING_LOGFILE = 'ceedling.log'

BACKTRACE_GDB_SCRIPT_FILE = 'backtrace.gdb'

INPUT_CONFIGURATION_CACHE_FILE = 'input.yml' unless defined?(INPUT_CONFIGURATION_CACHE_FILE) # input configuration file dump
DEFINES_DEPENDENCY_CACHE_FILE = 'defines_dependency.yml' unless defined?(DEFINES_DEPENDENCY_CACHE_FILE) # preprocessor definitions for files

Expand Down
22 changes: 10 additions & 12 deletions lib/ceedling/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,18 @@
}

DEFAULT_TEST_FIXTURE_TOOL = {
# Unity test runner executable
:executable => '${1}'.freeze,
:executable => '${1}'.freeze, # Unity test runner executable
:name => 'default_test_fixture'.freeze,
:optional => false.freeze,
:arguments => [].freeze
}

DEFAULT_TEST_FIXTURE_SIMPLE_BACKTRACE_TOOL = {
# Unity test runner executable
:executable => '${1}'.freeze,
:executable => '${1}'.freeze, # Unity test runner executable
:name => 'default_test_fixture_simple_backtrace'.freeze,
:optional => false.freeze,
:arguments => [
'-n ${2}' # Exact test case name matching flag
'-n ${2}'.freeze # Exact test case name matching flag
].freeze
}

Expand Down Expand Up @@ -240,13 +238,13 @@
# (Don't break a build if `gdb` is unavailable but backtrace does not require it.)
:optional => true.freeze,
:arguments => [
'-q',
'--eval-command run',
'--eval-command backtrace',
'--batch',
'--args',
'${1}',
'-n ${2}'
'-q'.freeze,
'--batch'.freeze,
'--eval-command run'.freeze,
"--command \"${1}\"".freeze, # Debug script file to run
'--args'.freeze,
'${2}'.freeze, # Test executable
'-n ${3}'.freeze # Exact test case name matching flag
].freeze
}

Expand Down
2 changes: 1 addition & 1 deletion lib/ceedling/generator_test_results.rb
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def extract_line_elements(executable, line, filename)
:test => elements[1],
:line => elements[0].to_i,
# Decode any multline strings
:message => message.nil? ? nil : message.gsub( '\n', "\n" ),
:message => message.nil? ? nil : message.gsub( NEWLINE_TOKEN, "\n" ),
:unity_test_time => unity_test_time
}

Expand Down
29 changes: 25 additions & 4 deletions lib/ceedling/generator_test_results_backtrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ def do_simple(filename, executable, shell_result, test_cases)
end

def do_gdb(filename, executable, shell_result, test_cases)
gdb_script_filepath = File.join( @configurator.project_build_tests_root, BACKTRACE_GDB_SCRIPT_FILE )

# Clean stats tracker
test_case_results = @RESULTS_COLLECTOR.new( passed:0, failed:0, ignored:0, output:[] )

Expand All @@ -87,6 +89,7 @@ def do_gdb(filename, executable, shell_result, test_cases)
# Build the test fixture to run with our test case of interest
command = @tool_executor.build_command_line(
@configurator.tools_backtrace_reporter, [],
gdb_script_filepath,
executable,
test_case[:test]
)
Expand Down Expand Up @@ -133,8 +136,8 @@ def do_gdb(filename, executable, shell_result, test_cases)

# Unity’s test executable output is line oriented.
# Multi-line output is not possible (it looks like random `printf()` statements to the results parser)
# "Encode" actual newlines as literal "\n"s (slash-n) to be handled by the test results parser.
test_output = crash_report.gsub( "\n", '\n' )
# "Encode" newlines in multiline string to be handled by the test results parser.
test_output = crash_report.gsub( "\n", NEWLINE_TOKEN )

test_output = "#{filename}:#{line_number}:#{test_case[:test]}:FAIL: Test case crashed >> #{test_output}"

Expand Down Expand Up @@ -164,19 +167,36 @@ def do_gdb(filename, executable, shell_result, test_cases)
private

def filter_gdb_test_report( report, test_case, filename )
# Sample `gdb` backtrace output
# =============================
# [Thread debugging using libthread_db enabled]
# Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
#
# Program received signal SIGSEGV, Segmentation fault.
# 0x0000555999f661fb in testCrash () at test/TestUsartModel.c:40
# 40 uint32_t i = *nullptr;
# #0 0x0000555999f661fb in testCrash () at test/TestUsartModel.c:40
# #1 0x0000555999f674de in run_test (func=0x555999f661e7 <testCrash>, name=0x555999f6b2e0 "testCrash", line_num=37) at build/test/runners/TestUsartModel_runner.c:76
# #2 0x0000555999f6766b in main (argc=3, argv=0x7fff917e2c98) at build/test/runners/TestUsartModel_runner.c:117

lines = report.split( "\n" )

report_start_index = 0
report_end_index = 0

# Find line before last occurrence of `<test_case>() at <filename>`
# Find line preceding last occurrence of `<test_case> () at <filename>`;
# it is the offending line of code.
# We don't need the rest of the call trace -- it's just from the runner
# up to the crashed test case.
lines.each_with_index do |line, index|
if line =~ /#{test_case}.+at.+#{filename}/
report_end_index = (index - 1) unless (index == 0)
end
end

# Work up the report to find the top of the containing text block
# Work back up from end index to find top line of the containing text block.
# Go until we find a blank line; then increment the index back down a line.
# This lops off any unneeded preamble.
report_end_index.downto(0).to_a().each do |index|
if lines[index].empty?
# Look for a blank line and adjust index to previous line of text
Expand All @@ -187,6 +207,7 @@ def filter_gdb_test_report( report, test_case, filename )

length = (report_end_index - report_start_index) + 1

# Reconstitute the report from the extracted lines
return lines[report_start_index, length].join( "\n" )
end

Expand Down

0 comments on commit cc0ad41

Please sign in to comment.