diff --git a/bin/projectinator.rb b/bin/projectinator.rb index 9e455d92..0ffb96c3 100644 --- a/bin/projectinator.rb +++ b/bin/projectinator.rb @@ -109,11 +109,11 @@ def extract_mixins(config:) # Handle any inline Ruby string expansion load_paths.each do |load_path| - load_path.replace( @system_wrapper.module_eval( load_path ) ) if (load_path =~ RUBY_STRING_REPLACEMENT_PATTERN) + load_path.replace( @system_wrapper.module_eval( load_path ) ) if (load_path =~ PATTERNS::RUBY_STRING_REPLACEMENT) end enabled.each do |mixin| - mixin.replace( @system_wrapper.module_eval( mixin ) ) if (mixin =~ RUBY_STRING_REPLACEMENT_PATTERN) + mixin.replace( @system_wrapper.module_eval( mixin ) ) if (mixin =~ PATTERNS::RUBY_STRING_REPLACEMENT) end # Remove the :mixins section of the configuration diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index 864d0cdf..1562ee75 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -132,7 +132,7 @@ 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) + path.replace( @system_wrapper.module_eval( path ) ) if (path =~ PATTERNS::RUBY_STRING_REPLACEMENT) FilePathUtils::standardize( path ) end @@ -460,7 +460,7 @@ def eval_environment_variables(config) # Process value array items.each do |item| # Process each item for Ruby string replacement - if item.is_a? String and item =~ RUBY_STRING_REPLACEMENT_PATTERN + if item.is_a? String and item =~ PATTERNS::RUBY_STRING_REPLACEMENT item.replace( @system_wrapper.module_eval( item ) ) end end @@ -706,7 +706,7 @@ def eval_path_entries( container ) end paths.each do |path| - path.replace( @system_wrapper.module_eval( path ) ) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) + path.replace( @system_wrapper.module_eval( path ) ) if (path =~ PATTERNS::RUBY_STRING_REPLACEMENT) end end @@ -721,7 +721,7 @@ def traverse_hash_eval_string_arrays(config) if config.all? { |item| item.is_a?( String ) } # Expand in place each string item in the array config.each do |item| - item.replace( @system_wrapper.module_eval( item ) ) if (item =~ RUBY_STRING_REPLACEMENT_PATTERN) + item.replace( @system_wrapper.module_eval( item ) ) if (item =~ PATTERNS::RUBY_STRING_REPLACEMENT) end end diff --git a/lib/ceedling/constants.rb b/lib/ceedling/constants.rb index a7a09c85..086c7429 100644 --- a/lib/ceedling/constants.rb +++ b/lib/ceedling/constants.rb @@ -65,6 +65,15 @@ class StdErrRedirect TCSH = :tcsh end +class PATTERNS + GLOB = /[\*\?\{\}\[\]]/ + RUBY_STRING_REPLACEMENT = /#\{.+\}/ + TOOL_EXECUTOR_ARGUMENT_REPLACEMENT = /(\$\{(\d+)\})/ + TEST_STDOUT_STATISTICS = /\n-+\s*(\d+)\s+Tests\s+(\d+)\s+Failures\s+(\d+)\s+Ignored\s+(OK|FAIL)\s*/i + TEST_SOURCE_FILE = /TEST_SOURCE_FILE\s*\(\s*\"\s*([^"]+)\s*\"\s*\)/ + TEST_INCLUDE_PATH = /TEST_INCLUDE_PATH\s*\(\s*\"\s*([^"]+)\s*\"\s*\)/ +end + GIT_COMMIT_SHA_FILENAME = 'GIT_COMMIT_SHA' # Escaped newline literal (literally double-slash-n) for "encoding" multiline strings as single string @@ -95,10 +104,6 @@ class StdErrRedirect UNITY_H_FILE = 'unity.h' UNITY_INTERNALS_H_FILE = 'unity_internals.h' -# Do-nothing macros defined in unity.h for extra build context to be used by build tools like Ceedling -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 @@ -134,12 +139,6 @@ class StdErrRedirect PREPROCESS_FULL_EXPANSION_DIR = 'full_expansion' PREPROCESS_DIRECTIVES_ONLY_DIR = 'directives_only' -# Match presence of any glob pattern characters -GLOB_PATTERN = /[\*\?\{\}\[\]]/ -RUBY_STRING_REPLACEMENT_PATTERN = /#\{.+\}/ -TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN = /(\$\{(\d+)\})/ -TEST_STDOUT_STATISTICS_PATTERN = /\n-+\s*(\d+)\s+Tests\s+(\d+)\s+Failures\s+(\d+)\s+Ignored\s+(OK|FAIL)\s*/i - NULL_FILE_PATH = '/dev/null' TESTS_BASE_PATH = TEST_ROOT_NAME diff --git a/lib/ceedling/file_path_utils.rb b/lib/ceedling/file_path_utils.rb index b3ee3ca2..2e61e36c 100644 --- a/lib/ceedling/file_path_utils.rb +++ b/lib/ceedling/file_path_utils.rb @@ -52,7 +52,7 @@ def self.no_decorators(path) path = self.no_aggregation_decorators(path) # Find first occurrence of glob specifier: *, ?, {, }, [, ] - find_index = (path =~ GLOB_PATTERN) + find_index = (path =~ PATTERNS::GLOB) # Return empty path if first character is part of a glob return '' if find_index == 0 diff --git a/lib/ceedling/generator_helper.rb b/lib/ceedling/generator_helper.rb index 3a9e7f30..d56dc1e0 100644 --- a/lib/ceedling/generator_helper.rb +++ b/lib/ceedling/generator_helper.rb @@ -26,7 +26,7 @@ def test_crash?(test_filename, executable, shell_result) end # No test results found in test executable output - if (shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN).nil? + if (shell_result[:output] =~ PATTERNS::TEST_STDOUT_STATISTICS).nil? # No debug logging here because we log this condition in the error log handling below crash = true end @@ -57,7 +57,7 @@ def log_test_results_crash(executable, shell_result, backtrace) notice += "> Produced no output (including no final test result counts).\n" # Check for no test results - elsif ((shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN).nil?) + elsif ((shell_result[:output] =~ PATTERNS::TEST_STDOUT_STATISTICS).nil?) # Mirror style of generic tool_executor failure output notice += "> Produced some output but contains no final test result counts.\n" end diff --git a/lib/ceedling/generator_test_results.rb b/lib/ceedling/generator_test_results.rb index 7f0962d5..d4626b15 100644 --- a/lib/ceedling/generator_test_results.rb +++ b/lib/ceedling/generator_test_results.rb @@ -105,7 +105,7 @@ def process_and_write_results(executable, unity_shell_result, results_file, test results[:time] = unity_shell_result[:time] unless unity_shell_result[:time].nil? # Process test statistics - if (unity_shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN) + if (unity_shell_result[:output] =~ PATTERNS::TEST_STDOUT_STATISTICS) results[:counts][:total] = $1.to_i results[:counts][:failed] = $2.to_i results[:counts][:ignored] = $3.to_i @@ -115,7 +115,7 @@ def process_and_write_results(executable, unity_shell_result, results_file, test end # Remove test statistics lines - output_string = unity_shell_result[:output].sub( TEST_STDOUT_STATISTICS_PATTERN, '' ) + output_string = unity_shell_result[:output].sub( PATTERNS::TEST_STDOUT_STATISTICS, '' ) # Process test executable results line-by-line output_string.lines do |line| diff --git a/lib/ceedling/include_pathinator.rb b/lib/ceedling/include_pathinator.rb index dbd6972f..f32344d9 100644 --- a/lib/ceedling/include_pathinator.rb +++ b/lib/ceedling/include_pathinator.rb @@ -28,7 +28,7 @@ def validate_test_build_directive_paths # TODO: When Ceedling's base project path handling is resolved, enable this path redefinition # path = File.join( @base_path, path ) unless @file_wrapper.exist?(path) - error = "'#{path}' specified by #{UNITY_TEST_INCLUDE_PATH}() within #{test_filepath} not found" + error = "'#{path}' specified by TEST_INCLUDE_PATH() within #{test_filepath} not found" raise CeedlingException.new( error ) end end @@ -51,7 +51,7 @@ def validate_header_files_collection if headers.length == 0 error = "No header files found in project.\n" + - "Add search paths to :paths ↳ :include in your project file and/or use #{UNITY_TEST_INCLUDE_PATH}() in your test files.\n" + + "Add search paths to :paths ↳ :include in your project file and/or use TEST_INCLUDE_PATH() in your test files.\n" + "Verify header files with `ceedling paths:include` and\\or `ceedling files:include`." @loginator.log( error, Verbosity::COMPLAIN ) end diff --git a/lib/ceedling/objects.yml b/lib/ceedling/objects.yml index f4e22481..af20521a 100644 --- a/lib/ceedling/objects.yml +++ b/lib/ceedling/objects.yml @@ -162,11 +162,14 @@ file_finder: file_finder_helper: compose: loginator +parsing_parcels: + test_context_extractor: compose: - configurator - file_wrapper - loginator + - parsing_parcels include_pathinator: compose: @@ -287,6 +290,8 @@ preprocessinator_file_handler: - loginator preprocessinator_extractor: + compose: + - parsing_parcels build_batchinator: compose: diff --git a/lib/ceedling/parsing_parcels.rb b/lib/ceedling/parsing_parcels.rb new file mode 100644 index 00000000..62182b6f --- /dev/null +++ b/lib/ceedling/parsing_parcels.rb @@ -0,0 +1,77 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/encodinator' + +# This is a collection of parsing aids to be used in other modules +class ParsingParcels + + # This parser accepts a collection of lines which it will sweep through and tidy, giving the purified + # lines to the block (one line at a time) for further analysis. It analyzes a single line at a time, + # which is far more memory efficient and faster for large files. However, this requires it to also + # handle backslash line continuations as a single line at this point. + def code_lines(input) + comment_block = false + full_line = '' + input.each_line do |line| + m = line.match /(.*)\\\s*$/ + if (!m.nil?) + full_line += m[1] + elsif full_line.empty? + _line, comment_block = clean_code_line( line, comment_block ) + yield( _line ) + else + _line, comment_block = clean_code_line( full_line + line, comment_block ) + yield( _line ) + full_line = '' + end + end + end + + private ###################################################################### + + def clean_code_line(line, comment_block) + _line = line.clean_encoding + + # Remove line comments + _line.gsub!(/\/\/.*$/, '') + + # Handle end of previously begun comment block + if comment_block + if _line.include?( '*/' ) + # Turn off comment block handling state + comment_block = false + + # Remove everything up to end of comment block + _line.gsub!(/^.*\*\//, '') + else + # Ignore contents of the line if its entirely within a comment block + return '', comment_block + end + + end + + # Block comments inside a C string are valid C, but we remove to simplify other parsing. + # No code we care about will be inside a C string. + # Note that we're not attempting the complex case of multiline string enclosed comment blocks + _line.gsub!(/"\s*\/\*.*"/, '') + + # Remove single-line block comments + _line.gsub!(/\/\*.*\*\//, '') + + # Handle beginning of any remaining multiline comment block + if _line.include?( '/*' ) + comment_block = true + + # Remove beginning of block comment + _line.gsub!(/\/\*.*/, '') + end + + return _line, comment_block + end + +end diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index 3fe5ca0b..d2d6fa2b 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -7,9 +7,12 @@ require 'ceedling/constants' require 'ceedling/encodinator' +require 'ceedling/parsing_parcels' class PreprocessinatorExtractor + constructor :parsing_parcels + ## ## Preprocessor Expansion Output Handling ## ====================================== @@ -138,11 +141,11 @@ def extract_test_directive_macro_calls(file_contents) # Look for TEST_SOURCE_FILE("...") and TEST_INCLUDE_PATH("...") in a string (i.e. a file's contents as a string) regexes = [ - /#{UNITY_TEST_SOURCE_FILE}.+?"\)/, - /#{UNITY_TEST_INCLUDE_PATH}.+?"\)/ + /(#{PATTERNS::TEST_SOURCE_FILE})/, + /(#{PATTERNS::TEST_INCLUDE_PATH})/ ] - return extract_tokens_by_regex_list( file_contents, *regexes ) + return extract_tokens_by_regex_list( file_contents, *regexes ).map(&:first) end @@ -199,7 +202,7 @@ def extract_multiline_directives(file_contents, directive) # - Captures all text (non-greedily) after '#' on a first line through 0 or more line continuations up to a final newline. # - Line continuations comprise a final '\' on a given line followed by whitespace & newline, wrapping to the next # line up to a final '\' on that next line. - regex = /(#\s*#{directive}\s+.*?(\\\s*\n.*?)*)\n/ + regex = /(#\s*#{directive}[^\n]*)\n/ tokens = extract_tokens_by_regex_list( file_contents, regex ) @@ -227,7 +230,9 @@ def extract_tokens_by_regex_list(file_contents, *regexes) # For each regex provided, extract all matches from the source string regexes.each do |regex| - tokens += file_contents.scan( regex ) + @parsing_parcels.code_lines( file_contents ) do |line| + tokens += line.scan( regex ) + end end return tokens diff --git a/lib/ceedling/test_context_extractor.rb b/lib/ceedling/test_context_extractor.rb index 7e848824..f51bf57e 100644 --- a/lib/ceedling/test_context_extractor.rb +++ b/lib/ceedling/test_context_extractor.rb @@ -12,7 +12,7 @@ class TestContextExtractor - constructor :configurator, :file_wrapper, :loginator + constructor :configurator, :file_wrapper, :loginator, :parsing_parcels def setup # Per test-file lookup hashes @@ -53,7 +53,7 @@ def collect_simple_context( filepath, input, *args ) source_extras = [] includes = [] - code_lines( input ) do |line| + @parsing_parcels.code_lines( input ) do |line| if args.include?( :build_directive_include_paths ) # Scan for build directives: TEST_INCLUDE_PATH() include_paths += extract_build_directive_include_paths( line ) @@ -99,7 +99,7 @@ def collect_test_runner_details(test_filepath, input_filepath=nil) def extract_includes(input) includes = [] - code_lines( input ) {|line| includes += _extract_includes( line ) } + @parsing_parcels.code_lines( input ) {|line| includes += _extract_includes( line ) } return includes.uniq end @@ -235,23 +235,13 @@ def ingest_includes(filepath, includes) end end - # Exposed for testing - def code_lines(input) - comment_block = false - # Far more memory efficient and faster (for large files) than slurping entire file into memory - input.each_line do |line| - _line, comment_block = clean_code_line( line, comment_block ) - yield( _line ) - end - end - private ################################# def collect_build_directive_source_files(filepath, files) ingest_build_directive_source_files( filepath, files.uniq ) debug_log_list( - "Extra source files found via #{UNITY_TEST_SOURCE_FILE}()", + "Extra source files found via TEST_SOURCE_FILE()", filepath, files ) @@ -261,7 +251,7 @@ def collect_build_directive_include_paths(filepath, paths) ingest_build_directive_include_paths( filepath, paths.uniq ) debug_log_list( - "Search paths for #includes found via #{UNITY_TEST_INCLUDE_PATH}()", + "Search paths for #includes found via TEST_INCLUDE_PATH()", filepath, paths ) @@ -293,8 +283,8 @@ def _collect_test_runner_details(filepath, test_content, input_content=nil) def extract_build_directive_source_files(line) source_extras = [] - # Look for TEST_SOURCE_FILE("<*>.<*>") statement - results = line.scan(/#{UNITY_TEST_SOURCE_FILE}\(\s*\"\s*(.+?\.\w+)*?\s*\"\s*\)/) + # Look for TEST_SOURCE_FILE("<*>") statement + results = line.scan(PATTERNS::TEST_SOURCE_FILE) results.each do |result| source_extras << FilePathUtils.standardize( result[0] ) end @@ -306,7 +296,7 @@ def extract_build_directive_include_paths(line) include_paths = [] # Look for TEST_INCLUDE_PATH("<*>") statements - results = line.scan(/#{UNITY_TEST_INCLUDE_PATH}\(\s*\"\s*(.+?)\s*\"\s*\)/) + results = line.scan(PATTERNS::TEST_INCLUDE_PATH) results.each do |result| include_paths << FilePathUtils.standardize( result[0] ) end @@ -371,46 +361,6 @@ def form_file_key( filepath ) return filepath.to_s.to_sym end - def clean_code_line(line, comment_block) - _line = line.clean_encoding - - # Remove line comments - _line.gsub!(/\/\/.*$/, '') - - # Handle end of previously begun comment block - if comment_block - if _line.include?( '*/' ) - # Turn off comment block handling state - comment_block = false - - # Remove everything up to end of comment block - _line.gsub!(/^.*\*\//, '') - else - # Ignore contents of the line if its entirely within a comment block - return '', comment_block - end - - end - - # Block comments inside a C string are valid C, but we remove to simplify other parsing. - # No code we care about will be inside a C string. - # Note that we're not attempting the complex case of multiline string enclosed comment blocks - _line.gsub!(/"\s*\/\*.*"/, '') - - # Remove single-line block comments - _line.gsub!(/\/\*.*\*\//, '') - - # Handle beginning of any remaining multiline comment block - if _line.include?( '/*' ) - comment_block = true - - # Remove beginning of block comment - _line.gsub!(/\/\*.*/, '') - end - - return _line, comment_block - end - def debug_log_list(message, filepath, list) msg = "#{message} in #{filepath}:" if list.empty? diff --git a/lib/ceedling/test_invoker_helper.rb b/lib/ceedling/test_invoker_helper.rb index 4a299844..56e7bba0 100644 --- a/lib/ceedling/test_invoker_helper.rb +++ b/lib/ceedling/test_invoker_helper.rb @@ -64,12 +64,12 @@ def validate_build_directive_source_files(test:, filepath:) end if not valid_extension - error = "File '#{source}' specified with #{UNITY_TEST_SOURCE_FILE}() in #{test} is not a #{ext_message} source file" + error = "File '#{source}' specified with TEST_SOURCE_FILE() in #{test} is not a #{ext_message} source file" raise CeedlingException.new(error) end if @file_finder.find_build_input_file(filepath: source, complain: :ignore, context: TEST_SYM).nil? - error = "File '#{source}' specified with #{UNITY_TEST_SOURCE_FILE}() in #{test} cannot be found in the source file collection" + error = "File '#{source}' specified with TEST_SOURCE_FILE() in #{test} cannot be found in the source file collection" raise CeedlingException.new(error) end end @@ -321,7 +321,7 @@ def generate_executable_now(context:, build_path:, executable:, objects:, flags: notice += "OPTIONS:\n" + " 1. Doublecheck this test's #include statements.\n" + " 2. Simplify complex macros or fully specify symbols for this test in :project ↳ :defines.\n" + - " 3. If no header file corresponds to the needed source file, use the #{UNITY_TEST_SOURCE_FILE}()\n" + + " 3. If no header file corresponds to the needed source file, use the TEST_SOURCE_FILE()\n" + " build diective macro in this test to inject a source file into the build.\n\n" + "See the docs on conventions, paths, preprocessing, compilation symbols, and build directive macros.\n\n" diff --git a/lib/ceedling/tool_executor.rb b/lib/ceedling/tool_executor.rb index d42efbae..80acd0c8 100644 --- a/lib/ceedling/tool_executor.rb +++ b/lib/ceedling/tool_executor.rb @@ -134,7 +134,7 @@ def expandify_element(tool_name, element, *args) args_index = 0 # handle ${#} input replacement - if (element =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN) + if (element =~ PATTERNS::TOOL_EXECUTOR_ARGUMENT_REPLACEMENT) args_index = ($2.to_i - 1) if (args.nil? or args[args_index].nil?) @@ -159,7 +159,7 @@ def expandify_element(tool_name, element, *args) end # handle inline ruby string substitution - if (build_string =~ RUBY_STRING_REPLACEMENT_PATTERN) + if (build_string =~ PATTERNS::RUBY_STRING_REPLACEMENT) build_string.replace(@system_wrapper.module_eval(build_string)) end @@ -187,7 +187,7 @@ def dehashify_argument_elements(tool_name, hash) expansion.each do |item| # String eval substitution - if (item =~ RUBY_STRING_REPLACEMENT_PATTERN) + if (item =~ PATTERNS::RUBY_STRING_REPLACEMENT) elements << @system_wrapper.module_eval(item) # Global constants elsif (@system_wrapper.constants_include?(item)) diff --git a/lib/ceedling/tool_validator.rb b/lib/ceedling/tool_validator.rb index 3b2c3a96..df608f7c 100644 --- a/lib/ceedling/tool_validator.rb +++ b/lib/ceedling/tool_validator.rb @@ -55,8 +55,8 @@ def validate_executable(tool:, name:, extension:, respect_optional:, boom:) # Skip everything if we've got an argument replacement pattern or Ruby string replacement in :executable # (Allow executable to be validated by shell at run time) - return true if (executable =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN) - return true if (executable =~ RUBY_STRING_REPLACEMENT_PATTERN) + return true if (executable =~ PATTERNS::TOOL_EXECUTOR_ARGUMENT_REPLACEMENT) + return true if (executable =~ PATTERNS::RUBY_STRING_REPLACEMENT) # Extract the executable (including optional filepath) apart from any additional arguments # Be mindful of legal quote enclosures (e.g. `"Code Cruncher" foo bar`) diff --git a/spec/parsing_parcels_spec.rb b/spec/parsing_parcels_spec.rb new file mode 100644 index 00000000..19e1f1db --- /dev/null +++ b/spec/parsing_parcels_spec.rb @@ -0,0 +1,66 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_helper' +require 'ceedling/parsing_parcels' + +describe ParsingParcels do + before(:each) do + + @parsing_parcels = described_class.new() + end + + context "#code_lines" do + it "should clean code of encoding problems and comments" do + file_contents = <<~CONTENTS + /* TEST_SOURCE_FILE("foo.c") */ // Eliminate single line comment block + // TEST_SOURCE_FILE("bar.c") // Eliminate single line comment + Some text⛔️ + /* // /* // Eliminate tricky comment block enclosing comments + TEST_SOURCE_FILE("boom.c") + */ // // Eliminate trailing single line comment following block comment + More text + #define STR1 "/* comment " // Strip out (single line) C string containing block comment + #define STR2 " /* comment " // Strip out (single line) C string containing block comment + CONTENTS + + got = [] + + @parsing_parcels.code_lines( StringIO.new( file_contents ) ) do |line| + line.strip! + got << line if !line.empty? + end + + expected = [ + 'Some text', # ⛔️ removed with encoding sanitizing + 'More text', + "#define STR1", + "#define STR2" + ] + + expect( got ).to eq expected + end + + it "should treat continuations as a single line" do + file_contents = "// TEST_SOURCE_FILE(\"foo.c\") \\ \nTEST_SOURCE_FILE(\"bar.c\")\nSome text⛔️ \\\nMore text\n" + got = [] + + @parsing_parcels.code_lines( StringIO.new( file_contents ) ) do |line| + line.strip! + got << line if !line.empty? + end + + expected = [ + 'Some text More text' + ] + + expect( got ).to eq expected + end + + end + +end diff --git a/spec/preprocessinator_extractor_spec.rb b/spec/preprocessinator_extractor_spec.rb index 6ed30ab4..843c6f48 100644 --- a/spec/preprocessinator_extractor_spec.rb +++ b/spec/preprocessinator_extractor_spec.rb @@ -5,9 +5,20 @@ # SPDX-License-Identifier: MIT # ========================================================================= +require 'spec_helper' require 'ceedling/preprocessinator_extractor' +require 'ceedling/parsing_parcels' describe PreprocessinatorExtractor do + before(:each) do + @parsing_parcels = ParsingParcels.new() + @extractor = described_class.new( + { + :parsing_parcels => @parsing_parcels + } + ) + end + context "#extract_file_as_array_from_expansion" do it "should simply extract text of original file from preprocessed expansion" do filepath = "path/to/WANT.c" @@ -32,7 +43,7 @@ input = StringIO.new( file_contents.join( "\n" ) ) - expect( subject.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected + expect( @extractor.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected end it "should extract text of original file from preprocessed expansion preserving #directives and cleaning up whitespace)" do @@ -62,7 +73,7 @@ input = StringIO.new( file_contents.join( "\n" ) ) - expect( subject.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected + expect( @extractor.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected end it "should extract text of original file from preprocessed expansion with complex preprocessor directive sequence" do @@ -97,7 +108,7 @@ input = StringIO.new( file_contents.join( "\n" ) ) - expect( subject.extract_file_as_array_from_expansion(input, filepath) ).to eq expected + expect( @extractor.extract_file_as_array_from_expansion(input, filepath) ).to eq expected end end @@ -125,7 +136,7 @@ input = StringIO.new( file_contents.join( "\n" ) ) - expect( subject.extract_file_as_string_from_expansion( input, filepath ) ).to eq expected + expect( @extractor.extract_file_as_string_from_expansion( input, filepath ) ).to eq expected end end @@ -146,7 +157,27 @@ 'TEST_INCLUDE_PATH("hello/there")' ] - expect( subject.extract_test_directive_macro_calls( file_text ) ).to eq expected + expect( @extractor.extract_test_directive_macro_calls( file_text ) ).to eq expected + end + end + + context "#extract_test_directive_macro_calls" do + it "should extract only uncommented calls" do + file_text = <<~FILE_TEXT + TEST_SOURCE_FILE("foo/bar/file.c")//TEST_SOURCE_FILE("yo/data.c") + + TEST_INCLUDE_PATH("some/inc/dir") + SOME_MACRO(TEST_INCLUDE_PATH("another/dir")) TEST_INCLUDE_PATH("hello/there") + FILE_TEXT + + expected = [ + 'TEST_SOURCE_FILE("foo/bar/file.c")', + 'TEST_INCLUDE_PATH("some/inc/dir")', + 'TEST_INCLUDE_PATH("another/dir")', + 'TEST_INCLUDE_PATH("hello/there")' + ] + + expect( @extractor.extract_test_directive_macro_calls( file_text ) ).to eq expected end end @@ -177,15 +208,15 @@ expected = [ "#pragma pack(1)", [ - "#pragma TOOL command \\", - " with_some_args \\", + "#pragma TOOL command ", + " with_some_args ", " that wrap" - ], + ].join, "#pragma warning(disable : 4996)", "#pragma GCC optimize(\"O3\")" ] - expect( subject.extract_pragmas( file_text ) ).to eq expected + expect( @extractor.extract_pragmas( file_text ) ).to eq expected end end @@ -200,7 +231,7 @@ #endif // _HEADER_INCLUDE_GUARD_ FILE_TEXT - expect( subject.extract_include_guard( file_text ) ).to eq '_HEADER_INCLUDE_GUARD_' + expect( @extractor.extract_include_guard( file_text ) ).to eq '_HEADER_INCLUDE_GUARD_' end it "should extract the first text that looks like an include guard from among file text" do @@ -216,7 +247,7 @@ #endif // HEADER_INCLUDE_GUARD FILE_TEXT - expect( subject.extract_include_guard( file_text ) ).to eq 'HEADER_INCLUDE_GUARD' + expect( @extractor.extract_include_guard( file_text ) ).to eq 'HEADER_INCLUDE_GUARD' end it "should not extract an include guard from among file text" do @@ -229,7 +260,7 @@ #endif // SOME_GUARD_NAME FILE_TEXT - expect( subject.extract_include_guard( file_text ) ).to eq nil + expect( @extractor.extract_include_guard( file_text ) ).to eq nil end end @@ -279,26 +310,26 @@ "#define SQUARE(x) ((x) * (x))", "#define MAX(a, b) ((a) > (b) ? (a) : (b))", [ - "#define MACRO(num, str) {\\", - " printf(\"%d\", num);\\", - " printf(\" is\"); \\", - " printf(\" %s number\", str);\\", - " printf(\"\\n\");\\", + "#define MACRO(num, str) {", + " printf(\"%d\", num);", + " printf(\" is\"); ", + " printf(\" %s number\", str);", + " printf(\"\\n\");", " }" - ], + ].join, [ - "#define LONG_STRING \"This is a very long string that \\", + "#define LONG_STRING \"This is a very long string that ", " continues on the next line\"" - ], + ].join, [ - "#define MULTILINE_MACRO do { \\", - " something(); \\", - " something_else(); \\", + "#define MULTILINE_MACRO do { ", + " something(); ", + " something_else(); ", " } while(0)" - ] + ].join ] - expect( subject.extract_macro_defs( file_text, nil ) ).to eq expected + expect( @extractor.extract_macro_defs( file_text, nil ) ).to eq expected end it "should ignore include guard among macro defintions in file text" do @@ -316,13 +347,10 @@ expected = [ "#define PI 3.14159", - [ - "#define LONG_STRING \"This is a very long string that \\", - " continues on the next line\"" - ] + "#define LONG_STRING \"This is a very long string that continues on the next line\"" ] - expect( subject.extract_macro_defs( file_text, '_INCLUDE_GUARD_' ) ).to eq expected + expect( @extractor.extract_macro_defs( file_text, '_INCLUDE_GUARD_' ) ).to eq expected end end diff --git a/spec/test_context_extractor_spec.rb b/spec/test_context_extractor_spec.rb index 1c7a8833..ab0d123a 100644 --- a/spec/test_context_extractor_spec.rb +++ b/spec/test_context_extractor_spec.rb @@ -7,11 +7,13 @@ require 'spec_helper' require 'ceedling/test_context_extractor' +require 'ceedling/parsing_parcels' require 'ceedling/exceptions' describe TestContextExtractor do before(:each) do # Mock injected dependencies + @parsing_parcels = ParsingParcels.new() @configurator = double( "Configurator" ) # Use double() so we can mock needed methods that are added dynamically at startup @file_wrapper = double( "FileWrapper" ) # Not actually exercised in these test cases loginator = instance_double( "Loginator" ) @@ -41,6 +43,7 @@ { :configurator => @configurator, :file_wrapper => @file_wrapper, + :parsing_parcels => @parsing_parcels, :loginator => loginator } ) @@ -94,39 +97,6 @@ end end - context "#code_lines" do - it "should clean code of encoding problems and comments" do - file_contents = <<~CONTENTS - /* TEST_SOURCE_FILE("foo.c") */ // Eliminate single line comment block - // TEST_SOURCE_FILE("bar.c") // Eliminate single line comment - Some text⛔️ - /* // /* // Eliminate tricky comment block enclosing comments - TEST_SOURCE_FILE("boom.c") - */ // // Eliminate trailing single line comment following block comment - More text - #define STR1 "/* comment " // Strip out (single line) C string containing block comment - #define STR2 " /* comment " // Strip out (single line) C string containing block comment - CONTENTS - - got = [] - - @extractor.code_lines( StringIO.new( file_contents ) ) do |line| - line.strip! - got << line if !line.empty? - end - - expected = [ - 'Some text', # ⛔️ removed with encoding sanitizing - 'More text', - "#define STR1", - "#define STR2" - ] - - expect( got ).to eq expected - end - - end - context "#extract_includes" do it "should extract #include directives from code" do # Complex comments tested in `clean_code_line()` test case