From a29922a4d6780dcb403792d763c30af51eb0389a Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Fri, 15 Nov 2024 13:00:51 -0500 Subject: [PATCH 01/35] =?UTF-8?q?=F0=9F=94=A5=20Removed=20:project=20?= =?UTF-8?q?=E2=86=B3=20:debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/BreakingChanges.md | 9 ++++++++- docs/Changelog.md | 7 ++++++- lib/ceedling/configurator.rb | 5 +---- lib/ceedling/defaults.rb | 3 +-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/BreakingChanges.md b/docs/BreakingChanges.md index 0939a766..36ad02a9 100644 --- a/docs/BreakingChanges.md +++ b/docs/BreakingChanges.md @@ -7,7 +7,7 @@ These breaking changes are complemented by two other documents: --- -# [1.0.0 pre-release] — 2024-10-31 +# [1.0.0 pre-release] — 2024-11-15 ## Explicit `:paths` ↳ `:include` entries in the project file @@ -243,3 +243,10 @@ The above subproject definition will now look like the following: - DEFINE_JUST_FOR_THIS_FILE - AND_ANOTHER ``` + +## Undocumented `:project` ↳ `:debug` has been removed + +This project setting existed from Ceedling’s earliest days and was a crude stand-in for command line debug verbosity handling. + +It has been removed as it was rarely if ever utilized and needlessly complicated internal mechanisms for verbosity handling and project validation. + diff --git a/docs/Changelog.md b/docs/Changelog.md index 8958f70b..4056b530 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -9,7 +9,7 @@ This changelog is complemented by two other documents: --- -# [1.0.0 pre-release] — 2024-10-31 +# [1.0.0 pre-release] — 2024-11-15 ## 🌟 Added @@ -463,4 +463,9 @@ When Ceedling was very young and tool definitions were relatively simple, Ceedli If you want to incorporate environment variables into your tool definitions, you may still do so. See the documentation for inline Ruby string exapnsion and the various options for defining or modifying a tool definition. In short, you may incorporate `"#{ENV['']}"` strings into your tooling. +### Undocumented `:project` ↳ `:debug` has been removed + +This project setting existed from Ceedling’s earliest days and was a crude stand-in for command line debug verbosity handling. + +It has been removed as it was rarely if ever utilized and needlessly complicated internal mechanisms for verbosity handling and project validation. diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index ac133807..5ca293f9 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -72,10 +72,7 @@ def reset_defaults(config) def set_verbosity(config) # 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]) + if (!!defined?(PROJECT_DEBUG) and PROJECT_DEBUG) eval("def project_debug() return true end", binding()) else eval("def project_debug() return false end", binding()) diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index cd222189..dc67ace5 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -296,8 +296,7 @@ :use_test_preprocessor => :none, :test_file_prefix => 'test_', :release_build => false, - :use_backtrace => :simple, - :debug => false + :use_backtrace => :simple }, :release_build => { From 4d4c55c710c4b4437da12ea280e365c79555a3f1 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Fri, 15 Nov 2024 13:04:33 -0500 Subject: [PATCH 02/35] =?UTF-8?q?=F0=9F=8E=A8=20More=20better=20preprocess?= =?UTF-8?q?or=20output=20handling=20&=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced memory hogging uses of `readlines()` - Updated tests and calls to use IO interface with File and StringIO for more realistic testing - Created first version of new directives-only output processing that scans from bottom of file - Began adding much more comprehensive comments --- lib/ceedling/preprocessinator_extractor.rb | 155 ++++++++++++++---- lib/ceedling/preprocessinator_file_handler.rb | 28 +++- spec/preprocessinator_extractor_spec.rb | 92 ++++++----- 3 files changed, 199 insertions(+), 76 deletions(-) diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index 30753f20..f1e7c513 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -6,56 +6,145 @@ # ========================================================================= class PreprocessinatorExtractor - def extract_base_file_from_preprocessed_expansion(filepath) - # preprocessing by way of toolchain preprocessor expands macros, eliminates - # comments, strips out #ifdef code, etc. however, it also expands in place - # each #include'd file. so, we must extract only the lines of the file - # that belong to the file originally preprocessed - - # iterate through all lines and alternate between extract and ignore modes - # all lines between a '#'line containing file name of our filepath and the - # next '#'line should be extracted - base_name = File.basename(filepath) - not_pragma = /^#(?!pragma\b)/ # preprocessor directive that's not a #pragma + + # Preprocessing expands macros, eliminates comments, strips out #ifdef code, etc. + # However, it also expands in place each #include'd file. So, we must extract + # only the lines of the file that belong to the file originally preprocessed. + + ## + ## Preprocessor Expansion Output Handling + ## ====================================== + ## + ## + ## Example preprocessed expansion output + ## -------------------------------------- + ## + ## # 14 "test/TestUsartModel.c" 2 + ## + ## void setUp(void) + ## { + ## } + ## + ## void tearDown(void) + ## { + ## } + ## + ## void testUsartModelInit(void) + ## { + ## TemperatureFilter_Init_CMockExpect(26); + ## + ## UsartModel_Init(); + ## } + ## # 55 "test/TestUsartModel.c" + ## void testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting(void) + ## { + ## uint8 dummyRegisterSetting = 17; + ## UsartModel_CalculateBaudRateRegisterSetting_CMockExpectAndReturn(58, 48054857, 115200, dummyRegisterSetting); + ## + ## UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )((dummyRegisterSetting)), (UNITY_INT)(UNITY_UINT8 )((UsartModel_GetBaudRateRegisterSetting())), (((void*)0)), (UNITY_UINT)(60), UNITY_DISPLAY_STYLE_UINT8); + ## } + ## + ## void testIgnore(void) + ## { + ## UnityIgnore( (((void*)0)), (UNITY_UINT)(65)); + ## } + ## # 75 "test/TestUsartModel.c" + ## void testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately(void) + ## { + ## TemperatureFilter_GetTemperatureInCelcius_CMockExpectAndReturn(77, 25.0f); + ## UnityAssertEqualString((const char*)(("25.0 C\n")), (const char*)((UsartModel_GetFormattedTemperature())), (((void*)0)), (UNITY_UINT)(78)); + ## } + ## + ## void testShouldReturnErrorMessageUponInvalidTemperatureValue(void) + ## { + ## TemperatureFilter_GetTemperatureInCelcius_CMockExpectAndReturn(83, -__builtin_huge_valf()); + ## UnityAssertEqualString((const char*)(("Temperature sensor failure!\n")), (const char*)((UsartModel_GetFormattedTemperature())), (((void*)0)), (UNITY_UINT)(84)); + ## } + ## + ## void testShouldReturnWakeupMessage(void) + ## { + ## UnityAssertEqualString((const char*)(("It's Awesome Time!\n")), (const char*)((UsartModel_GetWakeupMessage())), (((void*)0)), (UNITY_UINT)(89)); + ## } + + # `input` must have the interface of IO -- StringIO for testing or File in typical use + def extract_file_from_full_expansion(input, filepath) + + # Iterate through all lines and alternate between extract and ignore modes. + # All lines between a '#' line containing the file name of our filepath and the + # next '#' line should be extracted. + + base_name = File.basename( filepath ) pattern = /^#.*(\s|\/|\\|\")#{Regexp.escape(base_name)}/ - found_file = false # have we found the file we care about? + directive = /^#(?!pragma\b)/ # Preprocessor directive that's not a #pragma + extract = false # Found lines of file we care about? lines = [] - File.readlines(filepath).each do |line| + + # Use `each_line()` instead of `readlines()`. + # `each_line()` processes IO buffer one line at a time instead of all lines in an array. + # At large buffer sizes this is far more memory efficient and faster + input.each_line( chomp:true ) do |line| + + # Clean up any oddball characters in an otherwise ASCII document line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') - if found_file and not line =~ not_pragma - lines << line + + # Handle extract mode if the line is not a preprocessor directive + if extract and not line =~ directive + # Add the line with whitespace removed + lines << line.strip() + + # Otherwise the line contained a preprocessor directive; drop out of extract mode else - found_file = false + extract = false end - found_file = true if line =~ pattern + # Enter extract mode if the line is a preprocessor directive with filename of interest + extract = true if line =~ pattern end return lines end - def extract_base_file_from_preprocessed_directives(filepath) - # preprocessing by way of toolchain preprocessor eliminates directives only - # like #ifdef's and leave other code - # iterate through all lines and only get last chunk of file after a last - # '#'line containing file name of our filepath + # `input` must have the interface of IO -- StringIO for testing or File in typical use + # `buffer_size` exposed mostly for testing of stream handling + def extract_file_from_directives_only_expansion(input, filepath, buffer_size:256) + contents = "" base_name = File.basename(filepath) - pattern = /^#.*(\s|\/|\\|\")#{Regexp.escape(base_name)}/ - found_file = false # have we found the file we care about? + pattern = /(^#.+\".*#{Regexp.escape(base_name)}\"\s+\d+\s*\n)(.+)/m - lines = [] - File.readlines(filepath).each do |line| - line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') - lines << line + # Seek tracking and buffer size management + _buffer_size = [buffer_size, input.size()].min + read_total = 0 - if line =~ pattern - lines = [] - end - end + # Iteratively scan backwards until we find line matching regex pattern + while read_total < input.size() - return lines + # Move input pointer backward from end + input.seek( input.size() - read_total - _buffer_size, IO::SEEK_SET ) + + # Read from IO stream into a buffer + buffer = input.read( _buffer_size ) + + # Update total bytes read + read_total += _buffer_size + + # Determine next buffer read size -- minimum of target buffer size or remaining bytes in stream + _buffer_size = [buffer_size, (input.size() - read_total)].min + + # Inline handle any oddball bytes + buffer.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + + # Prepend bytes read to contents + contents = buffer + contents + + # Match on the pattern + match = pattern.match( contents ) + + # If a match, return everything after preprocessor directive line with filename of interest + return match[2] if !match.nil? + end end + end diff --git a/lib/ceedling/preprocessinator_file_handler.rb b/lib/ceedling/preprocessinator_file_handler.rb index d8879eba..b3f02472 100644 --- a/lib/ceedling/preprocessinator_file_handler.rb +++ b/lib/ceedling/preprocessinator_file_handler.rb @@ -5,6 +5,8 @@ # SPDX-License-Identifier: MIT # ========================================================================= +require 'rake' # for ext() method + class PreprocessinatorFileHandler constructor :preprocessinator_extractor, :configurator, :flaginator, :tool_executor, :file_path_utils, :file_wrapper, :loginator @@ -23,7 +25,17 @@ def preprocess_header_file(source_filepath:, preprocessed_filepath:, includes:, shell_result = @tool_executor.exec( command ) - contents = @preprocessinator_extractor.extract_base_file_from_preprocessed_expansion( preprocessed_filepath ) + # Preserve output from preprocessor + if @configurator.project_debug + _ext = File.extname( preprocessed_filepath ) + @file_wrapper.cp( preprocessed_filepath, preprocessed_filepath.ext( '.debug' + _ext ) ) + end + + contents = [] + + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + contents = @preprocessinator_extractor.extract_file_from_full_expansion( file, preprocessed_filepath ) + end # Reinsert #include statements into stripped down file # ---------------------------------------------------- @@ -65,6 +77,7 @@ def preprocess_header_file(source_filepath:, preprocessed_filepath:, includes:, # - Match (#include ")((path/)+)(file") and reassemble string using first and last matching groups contents.gsub!( /(#include\s+")(([^\/]+\/)+)(.+")/, '\1\4' ) + # Rewrite contents of file we originally loaded @file_wrapper.write( preprocessed_filepath, contents ) return shell_result @@ -82,7 +95,17 @@ def preprocess_test_file(source_filepath:, preprocessed_filepath:, includes:, fl shell_result = @tool_executor.exec( command ) - contents = @preprocessinator_extractor.extract_base_file_from_preprocessed_expansion( preprocessed_filepath ) + # Preserve output from preprocessor + if @configurator.project_debug + _ext = File.extname( preprocessed_filepath ) + @file_wrapper.cp( preprocessed_filepath, preprocessed_filepath.ext( '.debug' + _ext ) ) + end + + contents = [] + + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + contents = @preprocessinator_extractor.extract_file_from_full_expansion( file, preprocessed_filepath ) + end # Reinsert #include statements into stripped down file # ---------------------------------------------------- @@ -105,6 +128,7 @@ def preprocess_test_file(source_filepath:, preprocessed_filepath:, includes:, fl contents.gsub!( /(\n){2,}\}/, "\n}" ) # Collapse any unnecessary white space between code and closing function bracket contents.gsub!( /(\h*\n){3,}/, "\n\n" ) # Collapse repeated blank lines + # Rewrite contents of file we originally loaded @file_wrapper.write( preprocessed_filepath, contents ) return shell_result diff --git a/spec/preprocessinator_extractor_spec.rb b/spec/preprocessinator_extractor_spec.rb index 202c3bc4..eac4a50b 100644 --- a/spec/preprocessinator_extractor_spec.rb +++ b/spec/preprocessinator_extractor_spec.rb @@ -5,55 +5,58 @@ # SPDX-License-Identifier: MIT # ========================================================================= -# derived from test_graveyard/unit/preprocessinator_extractor_test.rb - -require 'spec_helper' require 'ceedling/preprocessinator_extractor' describe PreprocessinatorExtractor do - context "#extract_base_file_from_preprocessed_expansion" do - it "should extract text from the original file and keep #pragma statements" do - file_path = "path/to/WANT.c" - input_str = [ + context "#extract_file_from_full_expansion" do + it "should extract text of the original file from preprocessed expansion (and preserve #pragma statements)" do + filepath = "path/to/WANT.c" + + file_contents = [ '# 1 "some/file/we/do/not/care/about.c" 5', + '', '#pragma shit', 'some_text_we_do_not_want();', - '# 1 "some/file/we/DO/WANT.c" 99999', - 'some_text_we_do_not_want();', - '#pragma want', - 'some_awesome_text_we_want_so_hard();', - 'holy_crepes_more_awesome_text();', - '# oh darn', + '', + '# 1 "some/file/we/DO/WANT.c" 99999', # Beginning of block to extract + 'some_text_we_do_want();', # Line to extract + '#pragma want', # Line to extract (do not recognize #pragma as preprocessor directive) + 'some_awesome_text_we_want_so_hard();', # Line to extract + '', # Blank line + 'holy_crepes_more_awesome_text();', # Line to extract + ' ', # Blank line + '# oh darn', # End of block to extract (faux preprocessor directive) + '', '# 1 "some/useless/file.c" 9', 'a set of junk', 'more junk', - '# 1 "holy/shoot/yes/WANT.c" 10', - 'some_additional_awesome_want_text();', - ] + '', + '# 1 "holy/shoot/yes/WANT.c" 10', # Beginning of block to extract + 'some_additional_awesomely_wanted_text();', # Line to extract + ] # End of block to extract - expect_str = [ - 'some_text_we_do_not_want();', + expected = [ + 'some_text_we_do_want();', '#pragma want', 'some_awesome_text_we_want_so_hard();', + '', 'holy_crepes_more_awesome_text();', - 'some_additional_awesome_want_text();', + '', + 'some_additional_awesomely_wanted_text();', ] - expect(File).to receive(:readlines).with(file_path).and_return( input_str ) + input = StringIO.new( file_contents.join( "\n" ) ) - expect(subject.extract_base_file_from_preprocessed_expansion(file_path)).to eq expect_str + expect( subject.extract_file_from_full_expansion(input, filepath) ).to eq expected end - # These were originally hinted at by the old test, but we don't see anything - # in the implementation that does this. They are here as reminders in the future. - # # xit "should ignore formatting" - # # xit "should ignore whitespace" end - context "#extract_base_file_from_preprocessed_directives" do - it "should extract last chunk of text after last '#'line containing file name of our filepath" do - file_path = "path/to/WANT.c" - input_str = [ + context "#extract_file_from_directives_only_expansion" do + it "should extract last chunk of text after last '#' line containing file name of our filepath" do + filepath = "path/to/WANT.c" + + file_contents = [ '# 1 "some/file/we/do/not/care/about.c" 5', '#pragma trash', 'some_text_we_do_not_want();', @@ -64,25 +67,32 @@ '# 1 "some/useless/file.c" 9', 'a set of junk', 'more junk', - '# 1 "holy/shoot/yes/WANT.c" 10', + '# 1 "holy/shoot/yes/WANT.c" 10', # Beginning of block to extract '#pragma want', + '', '#define INCREDIBLE_DEFINE 911', + ' ', 'some_additional_awesome_want_text();', - 'holy_crepes_more_awesome_text();', - '# oh darn', + ' ', + 'holy_crepes_more_awesome_text();' ] - expect_str = [ - '#pragma want', - '#define INCREDIBLE_DEFINE 911', - 'some_additional_awesome_want_text();', - 'holy_crepes_more_awesome_text();', - '# oh darn', - ] + # Note spaces in empty lines of heredoc + expected = <<~EXTRACTED + #pragma want + + #define INCREDIBLE_DEFINE 911 + + some_additional_awesome_want_text(); + + holy_crepes_more_awesome_text(); + EXTRACTED + + expected.strip!() - expect(File).to receive(:readlines).with(file_path).and_return( input_str ) + input = StringIO.new( file_contents.join( "\n" ) ) - expect(subject.extract_base_file_from_preprocessed_directives(file_path)).to eq expect_str + expect( subject.extract_file_from_directives_only_expansion( input, filepath) ).to eq expected end end end From 937880c2da642e8c1ab05a4e74435cb007cfe86e Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Fri, 15 Nov 2024 20:26:07 -0500 Subject: [PATCH 03/35] =?UTF-8?q?=E2=9C=A8=20Preprocessing=20extraction=20?= =?UTF-8?q?of=20test=20&=20header=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reworked expansion extraction to be generally capable regardless of processing normal preprocessor expansion or direvtives only - Began routines for extracting test directive macros, macros definitions, and pragmas --- lib/ceedling/preprocessinator_extractor.rb | 99 +++++---- lib/ceedling/preprocessinator_file_handler.rb | 4 +- spec/preprocessinator_extractor_spec.rb | 205 +++++++++++++----- 3 files changed, 211 insertions(+), 97 deletions(-) diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index f1e7c513..eb1cc9ee 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -5,16 +5,24 @@ # SPDX-License-Identifier: MIT # ========================================================================= -class PreprocessinatorExtractor - - # Preprocessing expands macros, eliminates comments, strips out #ifdef code, etc. - # However, it also expands in place each #include'd file. So, we must extract - # only the lines of the file that belong to the file originally preprocessed. +require 'ceedling/constants' +class PreprocessinatorExtractor + ## ## Preprocessor Expansion Output Handling ## ====================================== + ## + ## Preprocessing expands macros, eliminates comments, strips out #ifdef code, etc. + ## However, it also expands in place each #include'd file. So, we must extract + ## only the lines of the file that belong to the file originally preprocessed. + ## + ## We do this by examininig each line and ping-ponging between extracting and + ## ignoring text based on preprocessor statements referencing the file we're + ## seeking to reassemble. ## + ## Note that the same text handling approach applies to full preprocessor + ## expansion as directives only expansion. ## ## Example preprocessed expansion output ## -------------------------------------- @@ -67,16 +75,21 @@ class PreprocessinatorExtractor ## } # `input` must have the interface of IO -- StringIO for testing or File in typical use - def extract_file_from_full_expansion(input, filepath) + def extract_file_as_array_from_expansion(input, filepath) # Iterate through all lines and alternate between extract and ignore modes. # All lines between a '#' line containing the file name of our filepath and the # next '#' line should be extracted. + # + # Notes: + # 1. Successive blocks can all be for the same source text file without terminating + # 2. The first line of the file could start a text block we care about + # 3. End of file could end a text block base_name = File.basename( filepath ) - pattern = /^#.*(\s|\/|\\|\")#{Regexp.escape(base_name)}/ - directive = /^#(?!pragma\b)/ # Preprocessor directive that's not a #pragma - extract = false # Found lines of file we care about? + directive = /^# \d+ \"/ + marker = /^# \d+ \".*#{Regexp.escape(base_name)}\"/ + extract = false lines = [] @@ -88,63 +101,65 @@ def extract_file_from_full_expansion(input, filepath) # Clean up any oddball characters in an otherwise ASCII document line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') - # Handle extract mode if the line is not a preprocessor directive + # Handle extraction if the line is not a preprocessor directive if extract and not line =~ directive - # Add the line with whitespace removed - lines << line.strip() - + _line = line.strip() + # Restore line if stripping leaves text + _line = line if !_line.empty? + lines << _line # Otherwise the line contained a preprocessor directive; drop out of extract mode else extract = false end # Enter extract mode if the line is a preprocessor directive with filename of interest - extract = true if line =~ pattern + extract = true if line =~ marker end return lines end - # `input` must have the interface of IO -- StringIO for testing or File in typical use - # `buffer_size` exposed mostly for testing of stream handling - def extract_file_from_directives_only_expansion(input, filepath, buffer_size:256) - contents = "" - - base_name = File.basename(filepath) - pattern = /(^#.+\".*#{Regexp.escape(base_name)}\"\s+\d+\s*\n)(.+)/m + # Simple variation of preceding that returns file contents as single string + def extract_file_as_string_from_expansion(input, filepath) + return extract_file_as_array_from_expansion(input, filepath).join( "\n" ) + end - # Seek tracking and buffer size management - _buffer_size = [buffer_size, input.size()].min - read_total = 0 - # Iteratively scan backwards until we find line matching regex pattern - while read_total < input.size() + # Extract all test directive macros as a list from a file as string + def extract_test_directive_macros(file_contents) + regexes = [ + /#{UNITY_TEST_SOURCE_FILE}.+?"\)/, + /#{UNITY_TEST_INCLUDE_PATH}.+?"\)/ + ] - # Move input pointer backward from end - input.seek( input.size() - read_total - _buffer_size, IO::SEEK_SET ) + return extract_tokens_by_regex_list( file_contents, regexes ) + end - # Read from IO stream into a buffer - buffer = input.read( _buffer_size ) + # Extract all macro definitions and pragmas as a list from a file as string + def extract_macros_defs_and_pragmas(file_contents) + regexes = [ + /(#\s*define\s+(\w+)(?:\s*\([^)]*\))?\s*((?:\\[ \t]*\n\s*)*.*))/m, + /(#pragma.+)\n/ + ] - # Update total bytes read - read_total += _buffer_size + tokens = extract_tokens_by_regex_list( file_contents, regexes ) - # Determine next buffer read size -- minimum of target buffer size or remaining bytes in stream - _buffer_size = [buffer_size, (input.size() - read_total)].min + return tokens.map {|token| token[0]} + end - # Inline handle any oddball bytes - buffer.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') + ### Private ### - # Prepend bytes read to contents - contents = buffer + contents + private - # Match on the pattern - match = pattern.match( contents ) + def extract_tokens_by_regex_list(file_contents, regexes) + tokens = [] - # If a match, return everything after preprocessor directive line with filename of interest - return match[2] if !match.nil? + regexes.each do |regex| + tokens += file_contents.scan( regex ) end + + return tokens end end diff --git a/lib/ceedling/preprocessinator_file_handler.rb b/lib/ceedling/preprocessinator_file_handler.rb index b3f02472..39cc46dc 100644 --- a/lib/ceedling/preprocessinator_file_handler.rb +++ b/lib/ceedling/preprocessinator_file_handler.rb @@ -34,7 +34,7 @@ def preprocess_header_file(source_filepath:, preprocessed_filepath:, includes:, contents = [] @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| - contents = @preprocessinator_extractor.extract_file_from_full_expansion( file, preprocessed_filepath ) + contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) end # Reinsert #include statements into stripped down file @@ -104,7 +104,7 @@ def preprocess_test_file(source_filepath:, preprocessed_filepath:, includes:, fl contents = [] @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| - contents = @preprocessinator_extractor.extract_file_from_full_expansion( file, preprocessed_filepath ) + contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) end # Reinsert #include statements into stripped down file diff --git a/spec/preprocessinator_extractor_spec.rb b/spec/preprocessinator_extractor_spec.rb index eac4a50b..a0951137 100644 --- a/spec/preprocessinator_extractor_spec.rb +++ b/spec/preprocessinator_extractor_spec.rb @@ -8,91 +8,190 @@ require 'ceedling/preprocessinator_extractor' describe PreprocessinatorExtractor do - context "#extract_file_from_full_expansion" do - it "should extract text of the original file from preprocessed expansion (and preserve #pragma statements)" do + 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" file_contents = [ '# 1 "some/file/we/do/not/care/about.c" 5', - '', - '#pragma shit', 'some_text_we_do_not_want();', - '', - '# 1 "some/file/we/DO/WANT.c" 99999', # Beginning of block to extract + '# 11 "some/file/we/DO/WANT.c" 99999', # Beginning of block to extract 'some_text_we_do_want();', # Line to extract - '#pragma want', # Line to extract (do not recognize #pragma as preprocessor directive) + '', # Blank line to extract 'some_awesome_text_we_want_so_hard();', # Line to extract - '', # Blank line 'holy_crepes_more_awesome_text();', # Line to extract - ' ', # Blank line - '# oh darn', # End of block to extract (faux preprocessor directive) - '', - '# 1 "some/useless/file.c" 9', - 'a set of junk', - 'more junk', - '', - '# 1 "holy/shoot/yes/WANT.c" 10', # Beginning of block to extract - 'some_additional_awesomely_wanted_text();', # Line to extract - ] # End of block to extract + '# 3 "some/other/file/we/ignore.c" 5', # End of block to extract + ] expected = [ 'some_text_we_do_want();', - '#pragma want', - 'some_awesome_text_we_want_so_hard();', '', - 'holy_crepes_more_awesome_text();', + 'some_awesome_text_we_want_so_hard();', + 'holy_crepes_more_awesome_text();' + ] + + input = StringIO.new( file_contents.join( "\n" ) ) + + expect( subject.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 + filepath = "this/path/MY_FILE.C" + + file_contents = [ + '# 1 "MY_FILE.C" 99999', # Beginning of block to extract + ' ', # Whitespace to clean up & preserve + '#pragma yo sup', # Line to extract -- #pragma is not end-of-block preprocessor directive + '#define FOO(...)', # Line to extract -- #define is not end-of-block preprocessor directive + 'void some_function(void) {', # Line to extract + ' do_something();', # Line to extract with leading whitespace that should remain + '}', # Line to extract + "\t", # Whitespace to clean up & preserve + '# 1 "some/useless/file.c"' # End of block to extract + ] + + expected = [ '', - 'some_additional_awesomely_wanted_text();', + '#pragma yo sup', + '#define FOO(...)', + 'void some_function(void) {', + ' do_something();', + '}', + '' ] input = StringIO.new( file_contents.join( "\n" ) ) - expect( subject.extract_file_from_full_expansion(input, filepath) ).to eq expected + expect( subject.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 + filepath = "dir/our_file.c" + + file_contents = [ + '# 1 "dir/our_file.c" 123', # Beginning of file / block to extract + 'some_text_we_do_want();', # Line to extract + 'some_awesome_text_we_want_so_hard();', # Line to extract + '# 987 "some preprocessor directive"', # End of block to extract (faux preprocessor directive) + '', + 'some_text_we_do_not_want();', + '# 15 "dir/our_file.c" 9', # Beginning of block to extract + 'more_text_we_want();', # Line to extract + 'void some_function(void) { func(); }', # Line to extract + '# 9 "dir/our_file.c" 77', # Continuation of block to extract + 'some code', # Line to extract + 'test statements', # Line to extract + '# 6 "dir/our_file.c" 19', # Continuation of block to extract + 'some_additional_awesomely_wanted_text();' # Line to extract + ] # End of file / end of block to extract + + expected = [ + 'some_text_we_do_want();', + 'some_awesome_text_we_want_so_hard();', + 'more_text_we_want();', + 'void some_function(void) { func(); }', + 'some code', + 'test statements', + 'some_additional_awesomely_wanted_text();' + ] + + input = StringIO.new( file_contents.join( "\n" ) ) + + expect( subject.extract_file_as_array_from_expansion(input, filepath) ).to eq expected + end end - context "#extract_file_from_directives_only_expansion" do - it "should extract last chunk of text after last '#' line containing file name of our filepath" do + context "#extract_file_as_string_from_expansion" do + it "should simply extract text of original file from preprocessed expansion" do filepath = "path/to/WANT.c" - + file_contents = [ '# 1 "some/file/we/do/not/care/about.c" 5', - '#pragma trash', 'some_text_we_do_not_want();', - '# 1 "some/file/we/DO/WANT.c" 99999', - 'some_text_we_do_not_want();', - '#pragma want', - 'some_creepy_text_we_not_want();', - '# 1 "some/useless/file.c" 9', - 'a set of junk', - 'more junk', - '# 1 "holy/shoot/yes/WANT.c" 10', # Beginning of block to extract - '#pragma want', + '# 11 "some/file/we/DO/WANT.c" 99999', # Beginning of block to extract + 'some_text_we_do_want();', # Line to extract + '', # Blank line to extract + 'some_awesome_text_we_want_so_hard();', # Line to extract + 'holy_crepes_more_awesome_text();', # Line to extract + '# 3 "some/other/file/we/ignore.c" 5', # End of block to extract + ] + + expected = [ + 'some_text_we_do_want();', '', - '#define INCREDIBLE_DEFINE 911', - ' ', - 'some_additional_awesome_want_text();', - ' ', + 'some_awesome_text_we_want_so_hard();', 'holy_crepes_more_awesome_text();' + ].join( "\n" ) + + input = StringIO.new( file_contents.join( "\n" ) ) + + expect( subject.extract_file_as_string_from_expansion( input, filepath ) ).to eq expected + end + end + + context "#extract_test_directive_macros" do + it "should extract any and all test directive macros from test file text" 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_SOURCE_FILE("yo/data.c")', + 'TEST_INCLUDE_PATH("some/inc/dir")', + 'TEST_INCLUDE_PATH("another/dir")', + 'TEST_INCLUDE_PATH("hello/there")' ] - # Note spaces in empty lines of heredoc - expected = <<~EXTRACTED - #pragma want - - #define INCREDIBLE_DEFINE 911 - - some_additional_awesome_want_text(); - - holy_crepes_more_awesome_text(); - EXTRACTED + expect( subject.extract_test_directive_macros( file_text ) ).to eq expected + end + end - expected.strip!() + context "#extract_macros_defs_and_pragmas" do + it "should extract any and all macro defintions and pragmas from header file text" do + file_text = <<~FILE_TEXT + SOME_MACRO("yo") - input = StringIO.new( file_contents.join( "\n" ) ) + #define PI 3.14159 + + #pragma pack(1) + + extern void func_sig(int, byte); + #define SQUARE(x) ((x) * (x)) + + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + + #pragma warning(disable : 4996) + #pragma GCC optimize("O3") + + #define MACRO(num, str) {\ + printf("%d", num);\ + printf(" is");\ + printf(" %s number", str);\ + printf("\n");\ + } + + SOME_OTHER_MACRO("more") + FILE_TEXT - expect( subject.extract_file_from_directives_only_expansion( input, filepath) ).to eq expected + expected = [ + '#define PI 3.14159', + '#define SQUARE(x) ((x) * (x))', + '#define MAX(a, b) ((a) > (b) ? (a) : (b))', + '#pragma warning(disable : 4996)', + '#pragma GCC optimize("O3")', + ] + + expect( subject.extract_macros_defs_and_pragmas( file_text ) ).to eq expected end end + + + + + + end From 222d8f7db60951a3d6a76ff864d2a77136a1aa77 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Sun, 17 Nov 2024 22:26:42 -0500 Subject: [PATCH 04/35] =?UTF-8?q?=E2=9C=A8=20First=20good=20extraction=20o?= =?UTF-8?q?f=20pragmas=20&=20#define?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/preprocessinator_extractor.rb | 40 ++++++--- spec/preprocessinator_extractor_spec.rb | 98 +++++++++++++++++----- 2 files changed, 108 insertions(+), 30 deletions(-) diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index eb1cc9ee..fa717249 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -127,32 +127,50 @@ def extract_file_as_string_from_expansion(input, filepath) # Extract all test directive macros as a list from a file as string - def extract_test_directive_macros(file_contents) + def extract_test_directive_macro_calls(file_contents) regexes = [ /#{UNITY_TEST_SOURCE_FILE}.+?"\)/, /#{UNITY_TEST_INCLUDE_PATH}.+?"\)/ ] - return extract_tokens_by_regex_list( file_contents, regexes ) + return extract_tokens_by_regex_list( file_contents, *regexes ) end - # Extract all macro definitions and pragmas as a list from a file as string - def extract_macros_defs_and_pragmas(file_contents) - regexes = [ - /(#\s*define\s+(\w+)(?:\s*\([^)]*\))?\s*((?:\\[ \t]*\n\s*)*.*))/m, - /(#pragma.+)\n/ - ] - tokens = extract_tokens_by_regex_list( file_contents, regexes ) + # Extract all pragmas as a list from a file as string + def extract_pragmas(file_contents) + tokens = extract_tokens_by_regex_list( file_contents, /#pragma.+$/ ) + return tokens.map {|token| token.rstrip()} + end + + + # Extract all macro definitions and pragmas as a list from a file as string + def extract_macro_defs(file_contents) + results = [] + + tokens = extract_tokens_by_regex_list( + file_contents, + /(#\s*define\s+.*?(\\\s*\n.*?)*)\n/ + ) + + tokens.each do |token| + multiline = token[0].split( "\n" ) + multiline.map! {|line| line.rstrip()} + if multiline.size == 1 + results << multiline[0] + else + results << multiline + end + end - return tokens.map {|token| token[0]} + return results end ### Private ### private - def extract_tokens_by_regex_list(file_contents, regexes) + def extract_tokens_by_regex_list(file_contents, *regexes) tokens = [] regexes.each do |regex| diff --git a/spec/preprocessinator_extractor_spec.rb b/spec/preprocessinator_extractor_spec.rb index a0951137..f56fba94 100644 --- a/spec/preprocessinator_extractor_spec.rb +++ b/spec/preprocessinator_extractor_spec.rb @@ -129,8 +129,8 @@ end end - context "#extract_test_directive_macros" do - it "should extract any and all test directive macros from test file text" do + context "#extract_test_directive_macro_calls" do + it "should extract any and all test directive macro calls from test file text" do file_text = <<~FILE_TEXT TEST_SOURCE_FILE("foo/bar/file.c")TEST_SOURCE_FILE("yo/data.c") @@ -146,46 +146,106 @@ 'TEST_INCLUDE_PATH("hello/there")' ] - expect( subject.extract_test_directive_macros( file_text ) ).to eq expected + expect( subject.extract_test_directive_macro_calls( file_text ) ).to eq expected end end - context "#extract_macros_defs_and_pragmas" do - it "should extract any and all macro defintions and pragmas from header file text" do + context "#extract_pragmas" do + it "should extract any and all pragmas from file text" do file_text = <<~FILE_TEXT SOME_MACRO("yo") - #define PI 3.14159 + #define PI 3.14159 - #pragma pack(1) + #pragma pack(1) extern void func_sig(int, byte); #define SQUARE(x) ((x) * (x)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) - #pragma warning(disable : 4996) + #pragma warning(disable : 4996) #pragma GCC optimize("O3") - #define MACRO(num, str) {\ - printf("%d", num);\ - printf(" is");\ - printf(" %s number", str);\ - printf("\n");\ + SOME_OTHER_MACRO("more") + FILE_TEXT + + expected = [ + "#pragma pack(1)", + "#pragma warning(disable : 4996)", + "#pragma GCC optimize(\"O3\")" + ] + + expect( subject.extract_pragmas( file_text ) ).to eq expected + end + end + + context "#extract_macro_defs" do + it "should extract any and all macro defintions from file text" do + + # Note aspects of this heredoc text block under test: + # - Macros beginning indented + # - Repeated whitespace characters + # - Single line and multiline macro definitions + # - Whitespace after continuation slashes (eliminated in extraction) + # - No empty lines between macro definitions + + file_text = <<~FILE_TEXT + SOME_MACRO("yo") + + #define PI 3.14159 + + #pragma GCC something + + extern void func_sig(int, byte); + #define SQUARE(x) ((x) * (x)) + + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + + extern void function(void); + + #define MACRO(num, str) {\\ + printf("%d", num);\\ + printf(" is"); \\ + printf(" %s number", str);\\ + printf("\\n");\\ } + #define LONG_STRING "This is a very long string that \\ + continues on the next line" + #define MULTILINE_MACRO do { \\ + something(); \\ + something_else(); \\ + } while(0) + SOME_OTHER_MACRO("more") FILE_TEXT expected = [ - '#define PI 3.14159', - '#define SQUARE(x) ((x) * (x))', - '#define MAX(a, b) ((a) > (b) ? (a) : (b))', - '#pragma warning(disable : 4996)', - '#pragma GCC optimize("O3")', + "#define PI 3.14159", + "#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 LONG_STRING \"This is a very long string that \\", + " continues on the next line\"" + ], + [ + "#define MULTILINE_MACRO do { \\", + " something(); \\", + " something_else(); \\", + " } while(0)" + ] ] - expect( subject.extract_macros_defs_and_pragmas( file_text ) ).to eq expected + expect( subject.extract_macro_defs( file_text ) ).to eq expected end end From 545eb0dde7c0c52a6a89348950c93f383e4f4598 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 12:33:25 -0500 Subject: [PATCH 05/35] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Added=20docs=20+=20m?= =?UTF-8?q?ultline=20pragmas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/preprocessinator_extractor.rb | 68 ++++++++++++++-------- spec/preprocessinator_extractor_spec.rb | 19 ++++-- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index fa717249..810b622c 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -82,20 +82,24 @@ def extract_file_as_array_from_expansion(input, filepath) # next '#' line should be extracted. # # Notes: - # 1. Successive blocks can all be for the same source text file without terminating - # 2. The first line of the file could start a text block we care about - # 3. End of file could end a text block + # 1. Successive blocks can all be from the same source text file without a different, intervening '#' line. + # Multiple back-to-back blocks could all begin with '# 99 "path/file.c"'. + # 2. The first line of the file could start a text block we care about. + # 3. End of file could end a text block. base_name = File.basename( filepath ) + # Preprocessor output blocks take the form of '# [optional digits]' directive = /^# \d+ \"/ + # Preprocessor output blocks for the file we care about take the form of '# "path/filename.ext" [optional digits]' marker = /^# \d+ \".*#{Regexp.escape(base_name)}\"/ + # Boolean to ping pong between line-by-line extract/ignore extract = false lines = [] - # Use `each_line()` instead of `readlines()`. - # `each_line()` processes IO buffer one line at a time instead of all lines in an array. - # At large buffer sizes this is far more memory efficient and faster + # Use `each_line()` instead of `readlines()` (chomp removes newlines). + # `each_line()` processes the IO buffer one line at a time instead of ingesting lines in an array. + # At large buffer sizes needed for potentially lengthy preprocessor output this is far more memory efficient and faster. input.each_line( chomp:true ) do |line| # Clean up any oddball characters in an otherwise ASCII document @@ -103,10 +107,12 @@ def extract_file_as_array_from_expansion(input, filepath) # Handle extraction if the line is not a preprocessor directive if extract and not line =~ directive + # Strip a line so we can omit useless blank lines _line = line.strip() - # Restore line if stripping leaves text - _line = line if !_line.empty? + # Restore text with left-side whitespace if previous stripping left some text + _line = line.rstrip() if !_line.empty? lines << _line + # Otherwise the line contained a preprocessor directive; drop out of extract mode else extract = false @@ -128,6 +134,8 @@ def extract_file_as_string_from_expansion(input, filepath) # Extract all test directive macros as a list from a file as string 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}.+?"\)/ @@ -139,40 +147,54 @@ def extract_test_directive_macro_calls(file_contents) # Extract all pragmas as a list from a file as string def extract_pragmas(file_contents) - tokens = extract_tokens_by_regex_list( file_contents, /#pragma.+$/ ) - return tokens.map {|token| token.rstrip()} + return extract_multiline_directives( file_contents, 'pragma' ) end - # Extract all macro definitions and pragmas as a list from a file as string + # Extract all macro definitions as a list from a file as string def extract_macro_defs(file_contents) + return extract_multiline_directives( file_contents, 'define' ) + end + + ### Private ### + + private + + def extract_multiline_directives(file_contents, directive) results = [] - tokens = extract_tokens_by_regex_list( - file_contents, - /(#\s*define\s+.*?(\\\s*\n.*?)*)\n/ - ) + # This regex captures any single or multiline preprocessor directive definition: + # - Looks for any string that begins with '#' ('#' and '' may be separated by spaces per C spec). + # - 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/ + + tokens = extract_tokens_by_regex_list( file_contents, regex ) tokens.each do |token| - multiline = token[0].split( "\n" ) - multiline.map! {|line| line.rstrip()} - if multiline.size == 1 - results << multiline[0] + # Get the full text string from `scan() results` and split it at any newlines + lines = token[0].split( "\n" ) + # Lop off any trailing whitespace (mostly to simplify unit testing) + lines.map! {|line| line.rstrip()} + + # If the result of splitting is just a single string, add it to the results array as a single string + if lines.size == 1 + results << lines[0] + # Otherwise, add the array of split strings to the results as a sub-array else - results << multiline + results << lines end end return results end - ### Private ### - - private def extract_tokens_by_regex_list(file_contents, *regexes) tokens = [] + # For each regex provided, extract all matches from the source string regexes.each do |regex| tokens += file_contents.scan( regex ) end diff --git a/spec/preprocessinator_extractor_spec.rb b/spec/preprocessinator_extractor_spec.rb index f56fba94..39afef1c 100644 --- a/spec/preprocessinator_extractor_spec.rb +++ b/spec/preprocessinator_extractor_spec.rb @@ -40,13 +40,13 @@ file_contents = [ '# 1 "MY_FILE.C" 99999', # Beginning of block to extract - ' ', # Whitespace to clean up & preserve + ' ', # Whitespace collapse & preserve as blank line '#pragma yo sup', # Line to extract -- #pragma is not end-of-block preprocessor directive '#define FOO(...)', # Line to extract -- #define is not end-of-block preprocessor directive 'void some_function(void) {', # Line to extract - ' do_something();', # Line to extract with leading whitespace that should remain - '}', # Line to extract - "\t", # Whitespace to clean up & preserve + ' do_something(); ', # Line to extract with leading whitespace that should remain + ' }', # Line to extract with leading whitespace that should remain + "\t", # Whitespace collapse & preserve as blank line '# 1 "some/useless/file.c"' # End of block to extract ] @@ -56,7 +56,7 @@ '#define FOO(...)', 'void some_function(void) {', ' do_something();', - '}', + ' }', '' ] @@ -162,6 +162,10 @@ extern void func_sig(int, byte); #define SQUARE(x) ((x) * (x)) + #pragma TOOL command \\ + with_some_args \\ + that wrap + #define MAX(a, b) ((a) > (b) ? (a) : (b)) #pragma warning(disable : 4996) @@ -172,6 +176,11 @@ expected = [ "#pragma pack(1)", + [ + "#pragma TOOL command \\", + " with_some_args \\", + " that wrap" + ], "#pragma warning(disable : 4996)", "#pragma GCC optimize(\"O3\")" ] From ba72b7f9a77df489912b0e37c7bc1aa6627dd4ed Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 22:58:24 -0500 Subject: [PATCH 06/35] =?UTF-8?q?=E2=9C=A8=20Added=20file=20read=20with=20?= =?UTF-8?q?optional=20length?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/file_wrapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ceedling/file_wrapper.rb b/lib/ceedling/file_wrapper.rb index 12326e9f..81411447 100644 --- a/lib/ceedling/file_wrapper.rb +++ b/lib/ceedling/file_wrapper.rb @@ -93,8 +93,8 @@ def open(filepath, flags) end end - def read(filepath) - return File.read(filepath) + def read(filepath, length=nil) + return File.read(filepath, length) end def touch(filepath, options={}) From 5b325c87eda5ceefb80499333e4b3c87f205730c Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 22:58:59 -0500 Subject: [PATCH 07/35] =?UTF-8?q?=E2=9C=A8=20Added=20new=20path=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/file_path_utils.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/ceedling/file_path_utils.rb b/lib/ceedling/file_path_utils.rb index b362ee8a..0d3f3e3f 100644 --- a/lib/ceedling/file_path_utils.rb +++ b/lib/ceedling/file_path_utils.rb @@ -151,12 +151,20 @@ def form_test_build_list_filepath(filepath) return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_list) ) end + def form_preprocessed_includes_list_filepath(filepath, subdir) + return File.join( @configurator.project_test_preprocess_includes_path, subdir, File.basename(filepath) + @configurator.extension_yaml ) + end + def form_preprocessed_file_filepath(filepath, subdir) return File.join( @configurator.project_test_preprocess_files_path, subdir, File.basename(filepath) ) end - def form_preprocessed_includes_list_filepath(filepath, subdir) - return File.join( @configurator.project_test_preprocess_includes_path, subdir, File.basename(filepath) + @configurator.extension_yaml ) + def form_preprocessed_file_full_expansion_filepath(filepath, subdir) + return File.join( @configurator.project_test_preprocess_files_path, subdir, 'full_expansion', File.basename(filepath) ) + end + + def form_preprocessed_file_directives_only_filepath(filepath, subdir) + return File.join( @configurator.project_test_preprocess_files_path, subdir, 'directives_only', File.basename(filepath) ) end def form_test_build_objects_filelist(path, sources) From 543ce85279d8c89444c6346206624e5db0e92469 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 22:59:29 -0500 Subject: [PATCH 08/35] =?UTF-8?q?=F0=9F=94=A5=20Removed=20unused=20depende?= =?UTF-8?q?ncy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/objects.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ceedling/objects.yml b/lib/ceedling/objects.yml index aba75aa0..1e6ec7e6 100644 --- a/lib/ceedling/objects.yml +++ b/lib/ceedling/objects.yml @@ -280,7 +280,6 @@ preprocessinator_file_handler: compose: - preprocessinator_extractor - configurator - - flaginator - tool_executor - file_path_utils - file_wrapper From dd4441e43de4529952b182331a8637e2825075a2 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 22:59:52 -0500 Subject: [PATCH 09/35] =?UTF-8?q?=F0=9F=94=A5=20Removed=20unused=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/configurator.rb | 17 ----------------- lib/ceedling/setupinator.rb | 3 --- 2 files changed, 20 deletions(-) diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index 5ca293f9..1b767c6e 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -49,23 +49,6 @@ def replace_flattened_config(config) end - def reset_defaults(config) - [:test_compiler, - :test_linker, - :test_fixture, - :test_includes_preprocessor, - :test_file_preprocessor, - :test_dependencies_generator, - :release_compiler, - :release_assembler, - :release_linker, - :release_dependencies_generator - ].each do |tool| - config[:tools].delete(tool) if (not (config[:tools][tool].nil?)) - end - end - - # Set up essential flattened config related to verbosity. # 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. diff --git a/lib/ceedling/setupinator.rb b/lib/ceedling/setupinator.rb index 1940e52b..56cdde1b 100644 --- a/lib/ceedling/setupinator.rb +++ b/lib/ceedling/setupinator.rb @@ -184,9 +184,6 @@ def do_setup( app_cfg ) @plugin_reportinator.set_system_objects( @ceedling ) end - def reset_defaults(config_hash) - @configurator.reset_defaults( config_hash ) - end ### Private From b621d60017cc53085c3b90332a515a9005e1573c Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 23:00:50 -0500 Subject: [PATCH 10/35] =?UTF-8?q?=E2=9C=A8=20Added=20directives=20only=20p?= =?UTF-8?q?reprprocessor=20tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/defaults.rb | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index dc67ace5..260ae86b 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -108,9 +108,9 @@ ].freeze } -DEFAULT_TEST_FILE_PREPROCESSOR_TOOL = { +DEFAULT_TEST_FILE_FULL_PREPROCESSOR_TOOL = { :executable => FilePathUtils.os_executable_ext('gcc').freeze, - :name => 'default_test_file_preprocessor'.freeze, + :name => 'default_test_file_full_preprocessor'.freeze, :optional => false.freeze, :arguments => [ '-E'.freeze, @@ -124,6 +124,23 @@ ].freeze } +DEFAULT_TEST_FILE_DIRECTIVES_ONLY_PREPROCESSOR_TOOL = { + :executable => FilePathUtils.os_executable_ext('gcc').freeze, + :name => 'default_test_file_directives_only_preprocessor'.freeze, + :optional => false.freeze, + :arguments => [ + '-E'.freeze, + "-I\"${4}\"".freeze, # Per-test executable search paths + "-D\"${3}\"".freeze, # Per-test executable defines + "-DGNU_COMPILER".freeze, # OSX clang + # '-nostdinc'.freeze, # disabled temporarily due to stdio access violations on OSX + "-x c".freeze, # Force C language + "-fdirectives-only", # Only preprocess directives + "\"${1}\"".freeze, + "-o \"${2}\"".freeze + ].freeze + } + # Disable the -MD flag for OSX LLVM Clang, since unsupported if RUBY_PLATFORM =~ /darwin/ && `gcc --version 2> /dev/null` =~ /Apple LLVM version .* \(clang/m # OSX w/LLVM Clang MD_FLAG = '' # Clang doesn't support the -MD flag @@ -252,9 +269,10 @@ DEFAULT_TOOLS_TEST_PREPROCESSORS = { :tools => { - :test_shallow_includes_preprocessor => DEFAULT_TEST_SHALLOW_INCLUDES_PREPROCESSOR_TOOL, - :test_nested_includes_preprocessor => DEFAULT_TEST_NESTED_INCLUDES_PREPROCESSOR_TOOL, - :test_file_preprocessor => DEFAULT_TEST_FILE_PREPROCESSOR_TOOL, + :test_shallow_includes_preprocessor => DEFAULT_TEST_SHALLOW_INCLUDES_PREPROCESSOR_TOOL, + :test_nested_includes_preprocessor => DEFAULT_TEST_NESTED_INCLUDES_PREPROCESSOR_TOOL, + :test_file_full_preprocessor => DEFAULT_TEST_FILE_FULL_PREPROCESSOR_TOOL, + :test_file_directives_only_preprocessor => DEFAULT_TEST_FILE_DIRECTIVES_ONLY_PREPROCESSOR_TOOL, } } From 444a50e6c495d4b7f3bee896dc0683eb410fee0f Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 23:01:37 -0500 Subject: [PATCH 11/35] =?UTF-8?q?=F0=9F=90=9B=20Prevented=20generated=20mo?= =?UTF-8?q?cks=20in=20includes=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/preprocessinator_includes_handler.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/ceedling/preprocessinator_includes_handler.rb b/lib/ceedling/preprocessinator_includes_handler.rb index 3f01f7ca..4e3d767c 100644 --- a/lib/ceedling/preprocessinator_includes_handler.rb +++ b/lib/ceedling/preprocessinator_includes_handler.rb @@ -313,10 +313,16 @@ def combine_mocks(*lists) # - Do not return mocks if mocking is disabled mocks = [] - if @configurator.project_use_mocks - # Use some greediness to ensure we get all possible mocks - lists.each { |list| mocks |= extract_mocks( list ) } - end + # Bail out early if mocks are not enabled + return [] if !@configurator.project_use_mocks + + # Use some greediness to ensure we get all possible mocks + lists.each { |list| mocks |= extract_mocks( list ) } + + # If generated mocks are in the build directory, the preprocessor will have found them. + # This leads to duplicated mocks -- the shallow list with no paths and the nested list with paths. + # Remove mocks with any path (the path will be the build directory); preserve just the shallow list. + mocks.reject! {|mock| File.dirname( mock ) != '.' } return mocks end From 8c234dc37ea2e10b83be78249cf291997d7951f4 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 23:02:19 -0500 Subject: [PATCH 12/35] =?UTF-8?q?=E2=9C=A8=20Additional=20preprocessing=20?= =?UTF-8?q?paths=20per=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/test_invoker.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/ceedling/test_invoker.rb b/lib/ceedling/test_invoker.rb index 665fa338..136ada00 100644 --- a/lib/ceedling/test_invoker.rb +++ b/lib/ceedling/test_invoker.rb @@ -54,6 +54,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) name = key.to_s build_path = File.join( @configurator.project_build_root, context.to_s, 'out', name ) mocks_path = File.join( @configurator.cmock_mock_path, name ) + preprocess_includes_path = File.join( @configurator.project_test_preprocess_includes_path, name ) preprocess_files_path = File.join( @configurator.project_test_preprocess_files_path, name ) @@ -71,10 +72,12 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) if @configurator.project_use_test_preprocessor != :none paths[:preprocess_incudes] = preprocess_includes_path paths[:preprocess_files] = preprocess_files_path + paths[:preprocess_files_full_expansion] = File.join( preprocess_files_path, 'full_expansion' ) + paths[:preprocess_files_directives_only] = File.join( preprocess_files_path, 'directives_only' ) end end - @testables[key][:paths].each {|_, path| @file_wrapper.mkdir(path) } + @testables[key][:paths].each {|_, path| @file_wrapper.mkdir( path ) } end # Remove any left over test results from previous runs @@ -227,7 +230,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) defines: testable[:preprocess_defines] } - @preprocessinator.preprocess_mockable_header_file(**arg_hash) + @preprocessinator.preprocess_mockable_header_file( **arg_hash ) end } if @configurator.project_use_mocks and @configurator.project_use_test_preprocessor_mocks From 55aa2b2d3645ddd19e12e21397ed28b1fa24be0d Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Mon, 18 Nov 2024 23:07:50 -0500 Subject: [PATCH 13/35] =?UTF-8?q?=E2=9C=A8=20First=20working=20version=20o?= =?UTF-8?q?f=20directive=20preservation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mockable header files now preserve any `#prgama` and `#define` statements through preprocessing - Test files now preserve test build directive macro calls through preprpcessing --- lib/ceedling/preprocessinator.rb | 49 ++++- lib/ceedling/preprocessinator_extractor.rb | 29 ++- lib/ceedling/preprocessinator_file_handler.rb | 185 ++++++++++++------ spec/preprocessinator_extractor_spec.rb | 68 ++++++- 4 files changed, 263 insertions(+), 68 deletions(-) diff --git a/lib/ceedling/preprocessinator.rb b/lib/ceedling/preprocessinator.rb index c1d410be..b8efd8ac 100644 --- a/lib/ceedling/preprocessinator.rb +++ b/lib/ceedling/preprocessinator.rb @@ -33,6 +33,7 @@ def preprocess_includes(filepath:, test:, flags:, include_paths:, defines:) includes_list_filepath = @file_path_utils.form_preprocessed_includes_list_filepath( filepath, test ) includes = [] + if @file_wrapper.newer?(includes_list_filepath, filepath) msg = @reportinator.generate_module_progress( operation: "Loading #include statement listing file for", @@ -56,6 +57,7 @@ def preprocess_includes(filepath:, test:, flags:, include_paths:, defines:) @loginator.log( msg, Verbosity::DEBUG ) @loginator.log( '', Verbosity::DEBUG ) + else includes = @includes_handler.extract_includes( filepath: filepath, @@ -108,20 +110,31 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de defines: defines } - # Extract shallow includes & print status message + # Extract includes & print status message includes = preprocess_file_common( **arg_hash ) arg_hash = { source_filepath: filepath, - preprocessed_filepath: preprocessed_filepath, - includes: includes, + test: test, flags: flags, include_paths: include_paths, defines: defines } + contents, extras = @file_handler.collect_header_file_contents( **arg_hash ) + # Run file through preprocessor & further process result - plugin_arg_hash[:shell_result] = @file_handler.preprocess_header_file( **arg_hash ) + # plugin_arg_hash[:shell_result] = @file_handler.preprocess_header_file( **arg_hash ) + + arg_hash = { + filename: File.basename( filepath ), + preprocessed_filepath: preprocessed_filepath, + contents: contents, + extras: extras, + includes: includes + } + + @file_handler.assemble_preprocessed_header_file( **arg_hash ) # Trigger post_mock_preprocessing plugin hook @plugin_manager.post_mock_preprocess( plugin_arg_hash ) @@ -153,20 +166,40 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:) defines: defines } - # Extract shallow includes & print status message + # Extract includes & print status message includes = preprocess_file_common( **arg_hash ) + # arg_hash = { + # source_filepath: filepath, + # preprocessed_filepath: preprocessed_filepath, + # includes: includes, + # flags: flags, + # include_paths: include_paths, + # defines: defines + # } + arg_hash = { source_filepath: filepath, - preprocessed_filepath: preprocessed_filepath, - includes: includes, + test: test, flags: flags, include_paths: include_paths, defines: defines } + contents, extras = @file_handler.collect_test_file_contents( **arg_hash ) + # Run file through preprocessor & further process result - plugin_arg_hash[:shell_result] = @file_handler.preprocess_test_file( **arg_hash ) + # plugin_arg_hash[:shell_result] = @file_handler.preprocess_test_file( **arg_hash ) + + arg_hash = { + filename: File.basename( filepath ), + preprocessed_filepath: preprocessed_filepath, + contents: contents, + extras: extras, + includes: includes + } + + @file_handler.assemble_preprocessed_test_file( **arg_hash ) # Trigger pre_mock_preprocessing plugin hook @plugin_manager.post_test_preprocess( plugin_arg_hash ) diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index 810b622c..3cd5de81 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -151,9 +151,34 @@ def extract_pragmas(file_contents) end + # Find include guard in file contents as string + def extract_include_guard(file_contents) + # Look for first occurrence of #ifndef followed by #define + regex = /#\s*ifndef\s+(\w+)(?:\s*\n)+\s*#\s*define\s+(\w+)/ + matches = file_contents.match( regex ) + + # Return if no match results + return nil if matches.nil? + + # Return if match results are not expected size + return nil if matches.size != 3 + + # Return if #ifndef does not match #define + return nil if matches[1] != matches[2] + + # Return string in common + return matches[1] + end + + # Extract all macro definitions as a list from a file as string - def extract_macro_defs(file_contents) - return extract_multiline_directives( file_contents, 'define' ) + def extract_macro_defs(file_contents, include_guard) + macro_definitions = extract_multiline_directives( file_contents, 'define' ) + + # Remove an include guard if provided + macro_definitions.reject! {|macro| macro.include?( include_guard ) } if !include_guard.nil? + + return macro_definitions end ### Private ### diff --git a/lib/ceedling/preprocessinator_file_handler.rb b/lib/ceedling/preprocessinator_file_handler.rb index 39cc46dc..0b8a1623 100644 --- a/lib/ceedling/preprocessinator_file_handler.rb +++ b/lib/ceedling/preprocessinator_file_handler.rb @@ -9,13 +9,17 @@ class PreprocessinatorFileHandler - constructor :preprocessinator_extractor, :configurator, :flaginator, :tool_executor, :file_path_utils, :file_wrapper, :loginator + constructor :preprocessinator_extractor, :configurator, :tool_executor, :file_path_utils, :file_wrapper, :loginator - def preprocess_header_file(source_filepath:, preprocessed_filepath:, includes:, flags:, include_paths:, defines:) - filename = File.basename(source_filepath) + def collect_header_file_contents(source_filepath:, test:, flags:, defines:, include_paths:) + contents = [] + pragmas = [] + macro_defs = [] + + preprocessed_filepath = @file_path_utils.form_preprocessed_file_full_expansion_filepath( source_filepath, test ) command = @tool_executor.build_command_line( - @configurator.tools_test_file_preprocessor, + @configurator.tools_test_file_full_preprocessor, flags, source_filepath, preprocessed_filepath, @@ -23,26 +27,40 @@ def preprocess_header_file(source_filepath:, preprocessed_filepath:, includes:, include_paths ) - shell_result = @tool_executor.exec( command ) + @tool_executor.exec( command ) - # Preserve output from preprocessor - if @configurator.project_debug - _ext = File.extname( preprocessed_filepath ) - @file_wrapper.cp( preprocessed_filepath, preprocessed_filepath.ext( '.debug' + _ext ) ) + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) end - contents = [] + preprocessed_filepath = @file_path_utils.form_preprocessed_file_directives_only_filepath( source_filepath, test ) + + command = @tool_executor.build_command_line( + @configurator.tools_test_file_directives_only_preprocessor, + flags, + source_filepath, + preprocessed_filepath, + defines, + include_paths + ) + + @tool_executor.exec( command ) + + include_guard = @preprocessinator_extractor.extract_include_guard( @file_wrapper.read( source_filepath, 2048 ) ) @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| - contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) + _contents = @preprocessinator_extractor.extract_file_as_string_from_expansion( file, preprocessed_filepath ) + + pragmas = @preprocessinator_extractor.extract_pragmas( _contents ) + macro_defs = @preprocessinator_extractor.extract_macro_defs( _contents, include_guard ) end - # Reinsert #include statements into stripped down file - # ---------------------------------------------------- - # Notes: - # - Preprocessing expands #includes, and we strip out those expansions. - # - #include order can be important. Iterating with unshift() inverts the order. So, we use revese(). - includes.reverse.each{ |include| contents.unshift( "#include \"#{include}\"" ) } + return contents, (pragmas + macro_defs) + end + + + def assemble_preprocessed_header_file(filename:, preprocessed_filepath:, contents:, extras:, includes:) + _contents = [] # Add #include guards for header files # Note: These aren't truly needed as preprocessed header files are only ingested by CMock. @@ -57,35 +75,64 @@ def preprocess_header_file(source_filepath:, preprocessed_filepath:, includes:, '' ] - # Add guards to beginning of file contents - contents = forward_guards + contents - contents += ["#endif // #{guardname}", ''] # Rear guard - # Insert Ceedling notice # ---------------------------------------------------- comment = "// CEEDLING NOTICE: This generated file only to be consumed by CMock" - contents = [comment, ''] + contents + _contents += [comment, ''] + + # Add guards to beginning of file contents + _contents += forward_guards + + # Blank line + _contents << '' + + # Reinsert #include statements into stripped down file + includes.each{ |include| _contents << "#include \"#{include}\"" } + + # Blank line + _contents << '' + + # Add in any macro defintions or prgamas + extras.each do |ex| + if ex.class == String + _contents << ex + + elsif ex.class == Array + _contents += ex + end + + # Blank line + _contents << '' + end + + _contents += contents + + _contents += ['', "#endif // #{guardname}", ''] # Rear guard # Write file, collapsing any repeated blank lines # ---------------------------------------------------- - contents = contents.join("\n") - contents.gsub!( /(\h*\n){3,}/, "\n\n" ) + _contents = _contents.join("\n") + _contents.gsub!( /(\h*\n){3,}/, "\n\n" ) # Remove paths from expanded #include directives # ---------------------------------------------------- # - We rely on search paths at compilation rather than explicit #include paths # - Match (#include ")((path/)+)(file") and reassemble string using first and last matching groups - contents.gsub!( /(#include\s+")(([^\/]+\/)+)(.+")/, '\1\4' ) + _contents.gsub!( /(#include\s+")(([^\/]+\/)+)(.+")/, '\1\4' ) - # Rewrite contents of file we originally loaded - @file_wrapper.write( preprocessed_filepath, contents ) - - return shell_result + # Write contents of final preprocessed file + @file_wrapper.write( preprocessed_filepath, _contents ) end - def preprocess_test_file(source_filepath:, preprocessed_filepath:, includes:, flags:, include_paths:, defines:) + + def collect_test_file_contents(source_filepath:, test:, flags:, defines:, include_paths:) + contents = [] + test_directives = [] + + preprocessed_filepath = @file_path_utils.form_preprocessed_file_full_expansion_filepath( source_filepath, test ) + command = @tool_executor.build_command_line( - @configurator.tools_test_file_preprocessor, + @configurator.tools_test_file_full_preprocessor, flags, source_filepath, preprocessed_filepath, @@ -93,45 +140,71 @@ def preprocess_test_file(source_filepath:, preprocessed_filepath:, includes:, fl include_paths ) - shell_result = @tool_executor.exec( command ) + @tool_executor.exec( command ) - # Preserve output from preprocessor - if @configurator.project_debug - _ext = File.extname( preprocessed_filepath ) - @file_wrapper.cp( preprocessed_filepath, preprocessed_filepath.ext( '.debug' + _ext ) ) + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) end - contents = [] + preprocessed_filepath = @file_path_utils.form_preprocessed_file_directives_only_filepath( source_filepath, test ) + + command = @tool_executor.build_command_line( + @configurator.tools_test_file_directives_only_preprocessor, + flags, + source_filepath, + preprocessed_filepath, + defines, + include_paths + ) + + @tool_executor.exec( command ) @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| - contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) + _contents = @preprocessinator_extractor.extract_file_as_string_from_expansion( file, preprocessed_filepath ) + + test_directives = @preprocessinator_extractor.extract_test_directive_macro_calls( _contents ) end - # Reinsert #include statements into stripped down file - # ---------------------------------------------------- - # Notes: - # - Preprocessing expands #includes, and we strip out those expansions. - # - #include order can be important. Iterating with unshift() inverts the order. So, we use revese(). - includes.reverse.each{ |include| contents.unshift( "#include \"#{include}\"" ) } + return contents, test_directives + end + + + def assemble_preprocessed_test_file(filename:, preprocessed_filepath:, contents:, extras:, includes:) + _contents = [] # Insert Ceedling notice # ---------------------------------------------------- comment = "// CEEDLING NOTICE: This generated file only to be consumed for test runner creation" - contents = [comment, ''] + contents + _contents += [comment, ''] - # Write file, doing some prettyifying along the way - # ---------------------------------------------------- - contents = contents.join("\n") - contents.gsub!( /^\s*;/, '' ) # Drop blank lines with semicolons left over from macro expansion + trailing semicolon - contents.gsub!( /\)\s+\{/, ")\n{" ) # Collapse any unnecessary white space between closing argument paren and opening function bracket - contents.gsub!( /\{(\n){2,}/, "{\n" ) # Collapse any unnecessary white space between opening function bracket and code - contents.gsub!( /(\n){2,}\}/, "\n}" ) # Collapse any unnecessary white space between code and closing function bracket - contents.gsub!( /(\h*\n){3,}/, "\n\n" ) # Collapse repeated blank lines + # Blank line + _contents << '' + + # Reinsert #include statements into stripped down file + includes.each{ |include| _contents << "#include \"#{include}\"" } + + # Blank line + _contents << '' - # Rewrite contents of file we originally loaded - @file_wrapper.write( preprocessed_filepath, contents ) + # Add in test directive macro calls + extras.each {|ex| _contents << ex} - return shell_result + # Blank line + _contents << '' + + _contents += contents + + # Write file, doing some prettyifying along the way + # ---------------------------------------------------- + _contents = _contents.join("\n") + _contents.gsub!( /^\s*;/, '' ) # Drop blank lines with semicolons left over from macro expansion + trailing semicolon + _contents.gsub!( /\)\s+\{/, ")\n{" ) # Collapse any unnecessary white space between closing argument paren and opening function bracket + _contents.gsub!( /\{(\n){2,}/, "{\n" ) # Collapse any unnecessary white space between opening function bracket and code + _contents.gsub!( /(\n){2,}\}/, "\n}" ) # Collapse any unnecessary white space between code and closing function bracket + _contents.gsub!( /(\h*\n){3,}/, "\n\n" ) # Collapse repeated blank lines + + # Write contents of final preprocessed file + @file_wrapper.write( preprocessed_filepath, _contents ) end end diff --git a/spec/preprocessinator_extractor_spec.rb b/spec/preprocessinator_extractor_spec.rb index 39afef1c..eabca929 100644 --- a/spec/preprocessinator_extractor_spec.rb +++ b/spec/preprocessinator_extractor_spec.rb @@ -189,6 +189,50 @@ end end + context "#extract_include_guard" do + it "should extract a simple include guard from among file text" do + file_text = <<~FILE_TEXT + #ifndef _HEADER_INCLUDE_GUARD_ + #define _HEADER_INCLUDE_GUARD_ + + ... + + #endif // _HEADER_INCLUDE_GUARD_ + FILE_TEXT + + expect( subject.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 + file_text = <<~FILE_TEXT + + #ifndef HEADER_INCLUDE_GUARD + + #define HEADER_INCLUDE_GUARD + + #ifndef DUMMY_INCLUDE_GUARD + #define DUMMY_INCLUDE_GUARD + + #endif // HEADER_INCLUDE_GUARD + FILE_TEXT + + expect( subject.extract_include_guard( file_text ) ).to eq 'HEADER_INCLUDE_GUARD' + end + + it "should not extract an include guard from among file text" do + file_text = <<~FILE_TEXT + #ifndef SOME_GUARD_NAME + #define OME_GUARD_NAME + + #define SOME_GUARD_NAME + + #endif // SOME_GUARD_NAME + FILE_TEXT + + expect( subject.extract_include_guard( file_text ) ).to eq nil + end + end + context "#extract_macro_defs" do it "should extract any and all macro defintions from file text" do @@ -254,13 +298,33 @@ ] ] - expect( subject.extract_macro_defs( file_text ) ).to eq expected + expect( subject.extract_macro_defs( file_text, nil ) ).to eq expected end - end + it "should ignore include guard among macro defintions in file text" do + file_text = <<~FILE_TEXT + #ifndef _INCLUDE_GUARD_ + #define _INCLUDE_GUARD_ + + #define PI 3.14159 + + #define LONG_STRING "This is a very long string that \\ + continues on the next line" + SOME_OTHER_MACRO("more") + FILE_TEXT + expected = [ + "#define PI 3.14159", + [ + "#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 + end + end end From e15cea431507d855354c83034d48c8fc1b33f351 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 21:57:33 -0500 Subject: [PATCH 14/35] Fixed unnecessary/ugly requires in helper --- spec/configurator_builder_spec.rb | 1 + spec/configurator_spec.rb | 1 + spec/spec_helper.rb | 4 +--- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/configurator_builder_spec.rb b/spec/configurator_builder_spec.rb index f2b0e3e6..5f271d1b 100644 --- a/spec/configurator_builder_spec.rb +++ b/spec/configurator_builder_spec.rb @@ -9,6 +9,7 @@ #derived from test_graveyard/unit/busted/configurator_builder_test.rb require 'spec_helper' +require 'ceedling/configurator_builder' describe ConfiguratorBuilder do xit "is scary" diff --git a/spec/configurator_spec.rb b/spec/configurator_spec.rb index fcb63688..7ea7ebb6 100644 --- a/spec/configurator_spec.rb +++ b/spec/configurator_spec.rb @@ -6,6 +6,7 @@ # ========================================================================= require 'spec_helper' +require 'ceedling/configurator' describe Configurator do describe "#standardize_paths" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 83bd9621..317dcb5c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,6 +16,7 @@ $: << File.join(here, '../lib') $: << File.join(here, '../vendor/cmock/lib') +$: << File.join(here, '../vendor/unity/auto') support_files = File.join(File.dirname(__FILE__), "support/**/*.rb") require_all Dir.glob(support_files, File::FNM_PATHNAME) @@ -26,8 +27,5 @@ # # ceedling_files = File.join(File.dirname(__FILE__), '../lib/**/*.rb') # # require_all Dir.glob(ceedling_files, File::FNM_PATHNAME) -require 'ceedling/preprocessinator_extractor' -require 'ceedling/configurator_builder' -require 'ceedling/configurator' From 030ead502d43b60a5a8f0cede8a92a8d3395e2fd Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 21:57:47 -0500 Subject: [PATCH 15/35] Parens --- lib/ceedling/test_invoker_helper.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ceedling/test_invoker_helper.rb b/lib/ceedling/test_invoker_helper.rb index c7835886..d3940f1b 100644 --- a/lib/ceedling/test_invoker_helper.rb +++ b/lib/ceedling/test_invoker_helper.rb @@ -24,15 +24,15 @@ class TestInvokerHelper :generator, :test_runner_manager - def setup + def setup() # Alias for brevity @batchinator = @build_batchinator end - def process_project_include_paths - @include_pathinator.validate_test_build_directive_paths - headers = @include_pathinator.validate_header_files_collection - @include_pathinator.augment_environment_header_files(headers) + def process_project_include_paths() + @include_pathinator.validate_test_build_directive_paths() + headers = @include_pathinator.validate_header_files_collection() + @include_pathinator.augment_environment_header_files( headers ) end def extract_include_directives(arg_hash) From 0cc6f25b11f9edb0b677b21896f43616452cfed2 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 21:58:27 -0500 Subject: [PATCH 16/35] =?UTF-8?q?=F0=9F=8E=A8=20Removed=20commented=20out?= =?UTF-8?q?=20old=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/preprocessinator.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/ceedling/preprocessinator.rb b/lib/ceedling/preprocessinator.rb index b8efd8ac..5d34598b 100644 --- a/lib/ceedling/preprocessinator.rb +++ b/lib/ceedling/preprocessinator.rb @@ -169,15 +169,6 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:) # Extract includes & print status message includes = preprocess_file_common( **arg_hash ) - # arg_hash = { - # source_filepath: filepath, - # preprocessed_filepath: preprocessed_filepath, - # includes: includes, - # flags: flags, - # include_paths: include_paths, - # defines: defines - # } - arg_hash = { source_filepath: filepath, test: test, From e5d17ce47900ae964893c2acf7f3a650f8cf1226 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 21:58:54 -0500 Subject: [PATCH 17/35] =?UTF-8?q?=F0=9F=92=A1=20Added=20comments=20before?= =?UTF-8?q?=20eventual=20bug=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/configurator.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index 1b767c6e..85c3b0d7 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -192,6 +192,8 @@ def populate_unity_config(config) msg = @reportinator.generate_progress( 'Processing Unity configuration' ) @loginator.log( msg, Verbosity::OBNOXIOUS ) + # :unity is not guaranteed to exist in a user configuration before populating it. + if config[:unity][:use_param_tests] config[:unity][:defines] << 'UNITY_SUPPORT_TEST_CASES' config[:unity][:defines] << 'UNITY_SUPPORT_VARIADIC_MACROS' @@ -200,6 +202,8 @@ def populate_unity_config(config) def populate_cmock_config(config) + # :unity is not guaranteed to exist in a user configuration before populating it. + # Populate config with CMock config cmock = config[:cmock] || {} @cmock_config = cmock From b75b523a73fc6be4fa7521e7b60eb74d4e78b993 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 22:04:47 -0500 Subject: [PATCH 18/35] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Text=20context=20ext?= =?UTF-8?q?raction=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Broke general test build directives handling into two distinct steps - Fixed recursion in rarely called `extract_includes()` - Fixed / improved code comments removal handling - Added unit tests --- .../preprocessinator_includes_handler.rb | 7 +- lib/ceedling/test_context_extractor.rb | 298 +++++++++----- lib/ceedling/test_invoker.rb | 20 +- spec/test_context_extractor_spec.rb | 362 ++++++++++++++++++ 4 files changed, 585 insertions(+), 102 deletions(-) create mode 100644 spec/test_context_extractor_spec.rb diff --git a/lib/ceedling/preprocessinator_includes_handler.rb b/lib/ceedling/preprocessinator_includes_handler.rb index 4e3d767c..5f481589 100644 --- a/lib/ceedling/preprocessinator_includes_handler.rb +++ b/lib/ceedling/preprocessinator_includes_handler.rb @@ -219,7 +219,12 @@ def extract_shallow_includes_regex(test:, filepath:, flags:, defines:) @loginator.log(msg, Verbosity::NORMAL) # Use abilities of @test_context_extractor to extract the #includes via regex on the file - return @test_context_extractor.extract_includes( filepath ) + includes = [] + @file_wrapper.open( filepath, 'r' ) do |file| + includes = @test_context_extractor.extract_includes( file ) + end + + return includes end def extract_nested_includes(filepath:, include_paths:, flags:, defines:, shallow:false) diff --git a/lib/ceedling/test_context_extractor.rb b/lib/ceedling/test_context_extractor.rb index 59437855..e256632d 100644 --- a/lib/ceedling/test_context_extractor.rb +++ b/lib/ceedling/test_context_extractor.rb @@ -6,6 +6,7 @@ # ========================================================================= require 'ceedling/exceptions' +require 'ceedling/file_path_utils' require 'ceedling/generator_test_runner' # From lib/ not vendor/unity/auto class TestContextExtractor @@ -13,41 +14,77 @@ class TestContextExtractor constructor :configurator, :file_wrapper, :loginator def setup - @all_header_includes = {} # Full list of all headers a test #includes + # Per test-file lookup hashes + @all_header_includes = {} # Full list of all headers from test #include statements @header_includes = {} # List of all headers minus mocks & framework files - @source_includes = {} - @source_extras = {} + @source_includes = {} # List of C files #include'd in a test file + @source_extras = {} # C source files outside of header convention added to test build by TEST_SOURCE_FILE() @test_runner_details = {} # Test case lists & Unity runner generator instances - @mocks = {} - @include_paths = {} - @all_include_paths = [] + @mocks = {} # List of mocks by name without header file extension + @include_paths = {} # Additional search paths added to a test build via TEST_INCLUDE_PATH() + + # Arrays + @all_include_paths = [] # List of all search paths added through individual test files using TEST_INCLUDE_PATH() @lock = Mutex.new end - def collect_simple_context( filepath, *args ) - content = @file_wrapper.read( filepath ) - content = sanitize_encoding( content ) - content_no_comments = remove_comments( content ) + # `input` must have the interface of IO -- StringIO for testing or File in typical use + def collect_simple_context( filepath, input, *args ) + all_options = [ + :build_directive_include_paths, + :build_directive_source_files, + :includes, + :test_runner_details + ] + # Code error check--bad context symbol argument args.each do |context| - case context - when :build_directive_macros - collect_build_directives( filepath, content_no_comments ) + next if context == :all + msg = "Unrecognized test context for collection :#{context}" + raise CeedlingException.new( msg ) if !all_options.include?( context ) + end - when :includes - collect_includes( filepath, content_no_comments ) + # Handle the :all shortcut to redefine list to include all contexts + args = all_options if args.include?( :all ) - when :test_runner_details - _collect_test_runner_details( filepath, content ) + include_paths = [] + source_extras = [] + includes = [] - else - raise CeedlingException.new( "Unrecognized test context for collection :#{context}" ) + 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 ) end + + if args.include?( :build_directive_source_files ) + # Scan for build directives: TEST_SOURCE_FILE() + source_extras += extract_build_directive_source_files( line ) + end + + if args.include?( :includes ) + # Scan for contents of #include directives + includes += _extract_includes( line ) + end + end + + collect_build_directive_include_paths( filepath, include_paths ) + collect_build_directive_source_files( filepath, source_extras ) + collect_includes( filepath, includes ) + + # Different code processing pattern for test runner + if args.include?( :test_runner_details ) + # Go back to beginning of IO object for a full string extraction + input.rewind() + + # Ultimately, we rely on Unity's runner generator that processes file contents as a single string + _collect_test_runner_details( filepath, input.read() ) end end def collect_test_runner_details(test_filepath, input_filepath=nil) + # Ultimately, we rely on Unity's runner generator that processes file contents as a single string _collect_test_runner_details( test_filepath, @file_wrapper.read( test_filepath ), @@ -55,13 +92,15 @@ def collect_test_runner_details(test_filepath, input_filepath=nil) ) end - # Scan for all includes - def extract_includes(filepath) - content = @file_wrapper.read( filepath ) - content = sanitize_encoding( content ) - content = remove_comments( content ) + # Scan for all includes. + # Unlike other extract() calls, extract_includes() is public to be called externally. + # `input` must have the interface of IO -- StringIO for testing or File in typical use + def extract_includes(input) + includes = [] + + code_lines( input ) {|line| includes += _extract_includes( line ) } - return extract_includes( filepath, content ) + return includes.uniq end # All header includes .h of test file @@ -110,9 +149,12 @@ def lookup_build_directive_sources_list(filepath) end def lookup_test_cases(filepath) - val = nil + val = [] @lock.synchronize do - val = @test_runner_details[form_file_key( filepath )][:test_cases] || [] + details = @test_runner_details[form_file_key( filepath )] + if !details.nil? + val = details[:test_cases] + end end return val end @@ -120,7 +162,10 @@ def lookup_test_cases(filepath) def lookup_test_runner_generator(filepath) val = nil @lock.synchronize do - val = @test_runner_details[form_file_key( filepath )][:generator] + details = @test_runner_details[form_file_key( filepath )] + if !details.nil? + val = details[:generator] + end end return val end @@ -148,6 +193,7 @@ def inspect_include_paths end end + # Unlike other ingest() calls, ingest_includes() can be called externally. def ingest_includes(filepath, includes) mock_prefix = @configurator.cmock_mock_prefix file_key = form_file_key( filepath ) @@ -188,59 +234,72 @@ def ingest_includes(filepath, includes) end end - private ################################# + # Exposed for testing (called from private `code_lines()`) + def clean_code_line(line, comment_block) + sanitize_encoding( line ) - # Scan for & store build directives - # - TEST_SOURCE_FILE() - # - TEST_INCLUDE_PATH() - # - # Note: This method is private unlike other `collect_ ()` methods. It is always - # called in the context collection process by way of `collect_context()`. - def collect_build_directives(filepath, content) - include_paths, source_extras = extract_build_directives( filepath, content ) + # Remove line comments + _line = line.gsub(/\/\/.*$/, '') - ingest_build_directives( - filepath: filepath, - include_paths: include_paths, - source_extras: source_extras - ) - end + # 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 - # Scan for & store includes (.h & .c) and mocks - # Note: This method is private unlike other `collect_ ()` methods. It is only - # called by way of `collect_context()`. - def collect_includes(filepath, content) - includes = _extract_includes( filepath, content ) - ingest_includes( filepath, includes ) - end + end - def extract_build_directives(filepath, content) - include_paths = [] - source_extras = [] + # 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!(/\/\*.*\*\//, '') - content.split("\n").each do |line| - # Look for TEST_INCLUDE_PATH("<*>") statements - results = line.scan(/#{UNITY_TEST_INCLUDE_PATH}\(\s*\"\s*(.+)\s*\"\s*\)/) - include_paths << FilePathUtils.standardize( results[0][0] ) if (results.size > 0) + # Handle beginning of any remaining multiline comment block + if _line.include?( '/*' ) + comment_block = true - # Look for TEST_SOURCE_FILE("<*>.<*>) statement - results = line.scan(/#{UNITY_TEST_SOURCE_FILE}\(\s*\"\s*(.+\.\w+)\s*\"\s*\)/) - source_extras << FilePathUtils.standardize( results[0][0] ) if (results.size > 0) + # Remove beginning of block comment + _line.gsub!(/\/\*.*/, '') end - return include_paths.uniq, source_extras.uniq + return _line, comment_block end - def _extract_includes(filepath, content) - includes = [] + private ################################# - content.split("\n").each do |line| - # Look for #include statements - results = line.scan(/#\s*include\s+\"\s*(.+)\s*\"/) - includes << results[0][0] if (results.size > 0) - end + def collect_build_directive_source_files(filepath, files) + ingest_build_directive_source_files( filepath, files.uniq ) - return includes.uniq + debug_log_list( + "Extra source files found via #{UNITY_TEST_SOURCE_FILE}()", + filepath, + files + ) + end + + 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}()", + filepath, + paths + ) + end + + def collect_includes(filepath, includes) + ingest_includes( filepath, includes.uniq ) + debug_log_list( "#includes found", filepath, includes ) end def _collect_test_runner_details(filepath, test_content, input_content=nil) @@ -255,29 +314,63 @@ def _collect_test_runner_details(filepath, test_content, input_content=nil) test_runner_generator: unity_test_runner_generator ) - msg = "Test cases found in #{filepath}:" test_cases = unity_test_runner_generator.test_cases - if test_cases.empty? - msg += " " - else - msg += "\n" - test_cases.each do |test_case| - msg += " - #{test_case[:line_number]}:#{test_case[:test]}()\n" - end + test_cases = test_cases.map {|test_case| "#{test_case[:line_number]}:#{test_case[:test]}()" } + + debug_log_list( "Test cases found ", filepath, test_cases ) + end + + 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*\)/) + results.each do |result| + source_extras << FilePathUtils.standardize( result[0] ) end - @loginator.log( msg, Verbosity::DEBUG ) + return source_extras end - def ingest_build_directives(filepath:, include_paths:, source_extras:) + 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.each do |result| + include_paths << FilePathUtils.standardize( result[0] ) + end + + return include_paths + end + + def _extract_includes(line) + includes = [] + + # Look for #include statements + results = line.match(/#\s*include\s+\"\s*((\w|\.)+)\s*\"/) + includes << results[1] if !results.nil? + + return includes + end + + ## + ## Data structure management ingest methods + ## + + def ingest_build_directive_source_files(filepath, source_extras) key = form_file_key( filepath ) @lock.synchronize do - @include_paths[key] = include_paths + @source_extras[key] = source_extras end + end + + def ingest_build_directive_include_paths(filepath, include_paths) + key = form_file_key( filepath ) @lock.synchronize do - @source_extras[key] = source_extras + @include_paths[key] = include_paths end @lock.synchronize do @@ -296,6 +389,23 @@ def ingest_test_runner_details(filepath:, test_runner_generator:) end end + ## + ## Utility methods + ## + + def form_file_key( filepath ) + return filepath.to_s.to_sym + end + + 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 + # Note: This method modifies encoding in place (encode!) in an attempt to reduce long string copies def sanitize_encoding(content) if not content.valid_encoding? @@ -304,21 +414,19 @@ def sanitize_encoding(content) return content end - # Note: This method is destructive to argument content in an attempt to reduce memory usage - def remove_comments(content) - _content = content.clone - - # Remove line comments - _content.gsub!(/\/\/.*$/, '') - - # Remove block comments - _content.gsub!(/\/\*.*?\*\//m, '') - - return _content - end + def debug_log_list(message, filepath, list) + msg = "#{message} in #{filepath}:" + if list.empty? + msg += " " + else + msg += "\n" + list.each do |item| + msg += " - #{item}\n" + end + end - def form_file_key( filepath ) - return filepath.to_s.to_sym + @loginator.log( msg, Verbosity::DEBUG ) + @loginator.log( '', Verbosity::DEBUG ) end end diff --git a/lib/ceedling/test_invoker.rb b/lib/ceedling/test_invoker.rb index 136ada00..d0a61e6f 100644 --- a/lib/ceedling/test_invoker.rb +++ b/lib/ceedling/test_invoker.rb @@ -94,14 +94,19 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) msg = @reportinator.generate_progress( "Parsing #{File.basename(filepath)} for build directive macros" ) @loginator.log( msg ) - # Just build directive macros (other context collected in later steps with help of preprocessing) - @context_extractor.collect_simple_context( filepath, :build_directive_macros ) + # Just build directive macros using simple text scanning. + # Other context collected in later steps with help of preprocessing. + @file_wrapper.open( filepath, 'r' ) do |input| + @context_extractor.collect_simple_context( filepath, input, :build_directive_source_files, :build_directive_include_paths ) + end else msg = @reportinator.generate_progress( "Parsing #{File.basename(filepath)} for build directive macros, #includes, and test case names" ) @loginator.log( msg ) - # Collect the works - @context_extractor.collect_simple_context( filepath, :build_directive_macros, :includes, :test_runner_details ) + # Collect everything using simple text scanning (no preprocessing involve). + @file_wrapper.open( filepath, 'r' ) do |input| + @context_extractor.collect_simple_context( filepath, input, :all ) + end end end @@ -286,7 +291,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) end } if @configurator.project_use_test_preprocessor_tests - # Build runners for all tests + # Generate runners for all tests @batchinator.build_step("Test Runners") do @batchinator.exec(workload: :compile, things: @testables) do |_, details| arg_hash = { @@ -437,7 +442,10 @@ def compile_test_component(tool:, context:TEST_SYM, test:, source:, object:, msg filepath = testable[:filepath] defines = testable[:compile_defines] - # Tailor search path--remove duplicates and reduce list to only those needed by vendor / support file compilation + # Tailor search path: + # 1. Remove duplicates. + # 2. If it's compilations of vendor / support files, reduce paths to only framework & support paths + # (e.g. we don't need all search paths to compile unity.c). search_paths = @helper.tailor_search_paths(search_paths:testable[:search_paths], filepath:source) # C files (user-configured extension or core framework file extensions) diff --git a/spec/test_context_extractor_spec.rb b/spec/test_context_extractor_spec.rb new file mode 100644 index 00000000..8ab94fa9 --- /dev/null +++ b/spec/test_context_extractor_spec.rb @@ -0,0 +1,362 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_helper' +require 'ceedling/test_context_extractor' +require 'ceedling/exceptions' + +describe TestContextExtractor do + before(:each) do + # Mock these injected dependencies + @configurator = instance_double("Configurator", :cmock_mock_prefix) + @file_wrapper = Object.new + loginator = instance_double("Loginator") + + # Ignore all logging calls + allow(loginator).to receive(:log) + + # Provide configurations + mock_prefix = 'mock_' + + # Rely on defaults in Unity's test runner generator + test_runner_config = { + :cmdline_args => false, + :mock_prefix => mock_prefix, + :mock_suffix => '', + :enforce_strict_ordering => false, + :defines => [], + :use_param_tests => false + } + + allow(@configurator).to receive(:cmock_mock_prefix).and_return( mock_prefix ) + allow(@configurator).to receive(:extension_header).and_return( '.h' ) + allow(@configurator).to receive(:extension_source).and_return( '.c' ) + allow(@configurator).to receive(:get_runner_config).and_return( test_runner_config ) + + @extractor = described_class.new( + { + :configurator => @configurator, + :file_wrapper => @file_wrapper, + :loginator => loginator + } + ) + end + + context "#lookup_full_header_includes_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_full_header_includes_list( "path" ) ).to eq [] + end + end + + context "#lookup_header_includes_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_header_includes_list( "path" ) ).to eq [] + end + end + + context "#lookup_include_paths_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_include_paths_list( "path" ) ).to eq [] + end + end + + context "#lookup_source_includes_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_source_includes_list( "path" ) ).to eq [] + end + end + + context "#lookup_build_directive_sources_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_build_directive_sources_list( "path" ) ).to eq [] + end + end + + context "#lookup_test_cases" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_test_cases( "path" ) ).to eq [] + end + end + + context "#lookup_test_runner_generator" do + it "should provide no generator when no context extraction has occurred" do + expect( @extractor.lookup_test_runner_generator( "path" ) ).to eq nil + end + end + + context "#lookup_raw_mock_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_raw_mock_list( "path" ) ).to eq [] + end + end + + context "#clean_code_line" do + it "should clean code of encoding problems and comments a line at a time" 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 block comment inside (single line) C string + #define STR2 " /* comment " // Strip out block comment inside (single line) C string + CONTENTS + + got = [] + comment_block = false + + file_contents.split( "\n" ).each do |line| + _line, comment_block = @extractor.clean_code_line( line, comment_block ) + _line.strip! + got << _line if !_line.empty? + end + + got.compact! + + expected = [ + 'Some text', + '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 + file_contents = <<~CONTENTS + #include "some_source.h" + #include "more_source.h" + + #include "some_source.h" // Duplicate to be ignored + + #include "unity.h" + + #include "mock_File.h" + #include "mock_another_file.h" + #include " mock_another_file.h " // Duplicate to be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + expected = [ + 'some_source.h', + 'more_source.h', + 'unity.h', + 'mock_File.h', + 'mock_another_file.h' + ] + + expect( @extractor.extract_includes( input ) ).to eq expected + end + end + + context "#collect_simple_context" do + it "should raise an execption for unknown symbol argument" do + expect{ @extractor.collect_simple_context( "path", StringIO.new(), :bad ) }.to raise_error( CeedlingException ) + end + + # collect_simple_context() + lookup_full_header_includes_list() + lookup_header_includes_list() + lookup_raw_mock_list() + it "should extract contents of #include directives" do + filepath = "path/tests/test_file.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + #include "some_source.h" + #include "more_source.h" + + #include "some_source.h" // Duplicate to be ignored + + #include "unity.h" + + #include "mock_File.h" + #include "mock_another_file.h" + #include " mock_another_file.h " // Duplicate to be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :includes ) + + expected_full = [ + 'some_source.h', + 'more_source.h', + 'unity.h', + 'mock_File.h', + 'mock_another_file.h' + ] + + expected_trim = [ + 'some_source.h', + 'more_source.h' + ] + + expected_mocks = [ + 'mock_File', + 'mock_another_file' + ] + + expect( @extractor.lookup_full_header_includes_list( filepath ) ).to eq expected_full + + expect( @extractor.lookup_header_includes_list( filepath ) ).to eq expected_trim + + expect( @extractor.lookup_raw_mock_list( filepath ) ).to eq expected_mocks + end + + # collect_simple_context() + lookup_build_directive_sources_list() + it "should extract extra source files by build directive macros" do + filepath = "path/tests/testfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_SOURE_FILE("bad_directive.c") // Typo in macro name that blocks recognition + + TEST_SOURCE_FILE("a.c") TEST_SOURCE_FILE("b.c") // Repeated calls on same line + + TEST_SOURCE_FILE("path/baz.c") // Leading whitespace to ignore + TEST_SOURCE_FILE( "some\\path\\boo.c" ) // Spaces in macro call + path separators to fix + + TEST_SOURCE_FILE() // Incomplete macro call that should be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_source_files ) + + expected = [ + 'a.c', + 'b.c', + 'path/baz.c', + 'some/path/boo.c' + ] + + expect( @extractor.lookup_build_directive_sources_list( filepath ) ).to eq expected + end + + # collect_simple_context() + lookup_include_paths_list() + it "should extract extra header search paths by build directive macros" do + filepath = "path/tests/testfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_INCLUE_PATH("bad_directive/") // Typo in macro name that blocks recognition + + TEST_INCLUDE_PATH("a") TEST_INCLUDE_PATH("b") // Repeated calls on same line + + TEST_INCLUDE_PATH("this/path") // Leading whitespace to ignore + TEST_INCLUDE_PATH( "some\\dir\\path/" ) // Spaces in macro call + path separators to fix + + TEST_INCLUDE_PATH() // Incomplete macro call that should be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) + + expected = [ + 'a', + 'b', + 'this/path', + 'some/dir/path' + ] + + expect( @extractor.lookup_include_paths_list( filepath ) ).to eq expected + end + + # collect_simple_context() + lookup_all_include_paths() + it "should extract extra header search paths for multiple files" do + # First File + filepath = "path/tests/testfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_INCLUDE_PATH("this/path") + TEST_INCLUDE_PATH("some/other/path") + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) + + # Second File + filepath = "anotherfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_INCLUDE_PATH("more/paths") + TEST_INCLUDE_PATH("yet/more/paths") + TEST_INCLUDE_PATH("this/path") // Duplicated from first file + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) + + expected = [ + 'this/path', + 'some/other/path', + 'more/paths', + 'yet/more/paths' + ] + + expect( @extractor.lookup_all_include_paths() ).to eq expected + end + + # collect_simple_context() + lookup_test_cases() + it "should extract test case names with line numbers" do + filepath = "path/tests/testfile.c" + + # Comments exercised because test case extraction relies on Unity's generate_test_runner.rb. + # Unity's Ruby code has its own handling of comments + file_contents = <<~CONTENTS + + void test_this_function() { + // TEST_ASSERT_TRUE( 1 == 1); + } + + /* + void test_this_other_function() { // Ignored due to comment block + TEST_ASSERT_FALSE( 0 == 1 ); + } + */ + + void test_another_function( void ) + { + // TEST_ASSERT_TRUE( 1 == 1); + } + + void TestME() { // Ignored because of naming mismatch + TEST_ASSERT_TRUE( 1 == 1); + } + + // void test_somestuff(void) { // Ignored due to comment lines + // // TEST_ASSERT_TRUE( 1 == 1); + // } + + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :test_runner_details ) + + expected = [ + {:line_number => 2, :test => 'test_this_function'}, + {:line_number => 12, :test => 'test_another_function'}, + ] + + expect( @extractor.lookup_test_cases( filepath ) ).to eq expected + end + + end + +end From 40cf92c1919c3640c35f17e0b29243de4c20cead Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 23:05:13 -0500 Subject: [PATCH 19/35] =?UTF-8?q?=F0=9F=8E=A8=20Path=20directories=20in=20?= =?UTF-8?q?common=20as=20constants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/constants.rb | 2 ++ lib/ceedling/file_path_utils.rb | 5 +++-- lib/ceedling/test_invoker.rb | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/ceedling/constants.rb b/lib/ceedling/constants.rb index c74b7cc8..74a9006d 100644 --- a/lib/ceedling/constants.rb +++ b/lib/ceedling/constants.rb @@ -131,6 +131,8 @@ class StdErrRedirect OPERATION_ASSEMBLE_SYM = :assemble unless defined?(OPERATION_ASSEMBLE_SYM) OPERATION_LINK_SYM = :link unless defined?(OPERATION_LINK_SYM) +PREPROCESS_FULL_EXPANSION_DIR = 'full_expansion' +PREPROCESS_DIRECTIVES_ONLY_DIR = 'directives_only' # Match presence of any glob pattern characters GLOB_PATTERN = /[\*\?\{\}\[\]]/ diff --git a/lib/ceedling/file_path_utils.rb b/lib/ceedling/file_path_utils.rb index 0d3f3e3f..bf1e1f3e 100644 --- a/lib/ceedling/file_path_utils.rb +++ b/lib/ceedling/file_path_utils.rb @@ -9,6 +9,7 @@ require 'rake' # for ext() require 'fileutils' require 'ceedling/system_wrapper' +require 'ceedling/constants' # global utility methods (for plugins, project files, etc.) def ceedling_form_filepath(destination_path, original_filepath, new_extension=nil) @@ -160,11 +161,11 @@ def form_preprocessed_file_filepath(filepath, subdir) end def form_preprocessed_file_full_expansion_filepath(filepath, subdir) - return File.join( @configurator.project_test_preprocess_files_path, subdir, 'full_expansion', File.basename(filepath) ) + return File.join( @configurator.project_test_preprocess_files_path, subdir, PREPROCESS_FULL_EXPANSION_DIR, File.basename(filepath) ) end def form_preprocessed_file_directives_only_filepath(filepath, subdir) - return File.join( @configurator.project_test_preprocess_files_path, subdir, 'directives_only', File.basename(filepath) ) + return File.join( @configurator.project_test_preprocess_files_path, subdir, PREPROCESS_DIRECTIVES_ONLY_DIR, File.basename(filepath) ) end def form_test_build_objects_filelist(path, sources) diff --git a/lib/ceedling/test_invoker.rb b/lib/ceedling/test_invoker.rb index d0a61e6f..fb7772ae 100644 --- a/lib/ceedling/test_invoker.rb +++ b/lib/ceedling/test_invoker.rb @@ -72,8 +72,8 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) if @configurator.project_use_test_preprocessor != :none paths[:preprocess_incudes] = preprocess_includes_path paths[:preprocess_files] = preprocess_files_path - paths[:preprocess_files_full_expansion] = File.join( preprocess_files_path, 'full_expansion' ) - paths[:preprocess_files_directives_only] = File.join( preprocess_files_path, 'directives_only' ) + paths[:preprocess_files_full_expansion] = File.join( preprocess_files_path, PREPROCESS_FULL_EXPANSION_DIR ) + paths[:preprocess_files_directives_only] = File.join( preprocess_files_path, PREPROCESS_DIRECTIVES_ONLY_DIR ) end end From 613b8aa37f20f3dd2ed898f2d13e32709abbd523 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 23:07:42 -0500 Subject: [PATCH 20/35] =?UTF-8?q?=F0=9F=90=9B=20Fixes=20&=20test=20coverag?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed RSpec test double problem that was showing up in the full suite but not in individual spec runs - Added test coverage for non-ASCII encoding with fixes to the source to achieve expected results --- lib/ceedling/test_context_extractor.rb | 16 ++++++++++------ spec/test_context_extractor_spec.rb | 16 ++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/ceedling/test_context_extractor.rb b/lib/ceedling/test_context_extractor.rb index e256632d..86d9d32f 100644 --- a/lib/ceedling/test_context_extractor.rb +++ b/lib/ceedling/test_context_extractor.rb @@ -236,10 +236,10 @@ def ingest_includes(filepath, includes) # Exposed for testing (called from private `code_lines()`) def clean_code_line(line, comment_block) - sanitize_encoding( line ) + _line = sanitize_encoding( line ) # Remove line comments - _line = line.gsub(/\/\/.*$/, '') + _line.gsub!(/\/\/.*$/, '') # Handle end of previously begun comment block if comment_block @@ -408,10 +408,14 @@ def code_lines(input) # Note: This method modifies encoding in place (encode!) in an attempt to reduce long string copies def sanitize_encoding(content) - if not content.valid_encoding? - content.encode!("UTF-16be", :invalid=>:replace, :replace=>"?").encode('UTF-8') - end - return content + encoding_options = { + :invalid => :replace, # Replace invalid byte sequences + :undef => :replace, # Replace anything not defined in ASCII + :replace => '', # Use a blank for those replacements + :universal_newline => true # Always break lines with \n + } + + return content.encode("ASCII", **encoding_options).encode('UTF-8') end def debug_log_list(message, filepath, list) diff --git a/spec/test_context_extractor_spec.rb b/spec/test_context_extractor_spec.rb index 8ab94fa9..07caaf5e 100644 --- a/spec/test_context_extractor_spec.rb +++ b/spec/test_context_extractor_spec.rb @@ -11,10 +11,10 @@ describe TestContextExtractor do before(:each) do - # Mock these injected dependencies - @configurator = instance_double("Configurator", :cmock_mock_prefix) - @file_wrapper = Object.new - loginator = instance_double("Loginator") + # Mock injected dependencies + @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" ) # Ignore all logging calls allow(loginator).to receive(:log) @@ -99,13 +99,13 @@ file_contents = <<~CONTENTS /* TEST_SOURCE_FILE("foo.c") */ // Eliminate single line comment block // TEST_SOURCE_FILE("bar.c") // Eliminate single line comment - Some text + 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 block comment inside (single line) C string - #define STR2 " /* comment " // Strip out block comment inside (single line) C string + #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 = [] @@ -120,7 +120,7 @@ got.compact! expected = [ - 'Some text', + 'Some text', # ⛔️ removed with encoding sanitizing 'More text', "#define STR1", "#define STR2" From f7b3de921dca31f80f89e3f98e5b09c562498dc6 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 23:08:11 -0500 Subject: [PATCH 21/35] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20More=20better=20non-?= =?UTF-8?q?ASCII=20encoding=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/preprocessinator_extractor.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index 3cd5de81..9ec6397d 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -103,8 +103,14 @@ def extract_file_as_array_from_expansion(input, filepath) input.each_line( chomp:true ) do |line| # Clean up any oddball characters in an otherwise ASCII document - line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') - + encoding_options = { + :invalid => :replace, # Replace invalid byte sequences + :undef => :replace, # Replace anything not defined in ASCII + :replace => '', # Use a blank for those replacements + :universal_newline => true # Always break lines with \n + } + line = line.encode("ASCII", **encoding_options).encode('UTF-8') + # Handle extraction if the line is not a preprocessor directive if extract and not line =~ directive # Strip a line so we can omit useless blank lines From 4d38cba8d908ce54835c5f18932166404ce4ebb8 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 27 Nov 2024 23:28:00 -0500 Subject: [PATCH 22/35] =?UTF-8?q?=E2=9C=A8=20TEST=5FSOURCE=5FFILE()=20can?= =?UTF-8?q?=20now=20be=20within=20#ifdef?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CeedlingPacket.md | 48 ++++++++++++++++++++++++------------ lib/ceedling/test_invoker.rb | 26 +++++++++++-------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/docs/CeedlingPacket.md b/docs/CeedlingPacket.md index 2bbc6b7e..54c16e84 100644 --- a/docs/CeedlingPacket.md +++ b/docs/CeedlingPacket.md @@ -1546,22 +1546,38 @@ limitation that affects Unity features triggered by the following macros:** With Ceedling’s preprocessing enabled, Unity’s `TEST_CASE()` and `TEST_RANGE()` in your test files will effectively vanish before test compilation is able to -process them. That is, Ceedling’s preprocessing and these Unity features are -not presently compatible. (Note that it is possible to enable preprocessing for -mockable header files apart from enabling it for test files.) - -**_NOTE:_ The following new build directive macros available in Ceedling 1.0.0 -are incompatible with enclosing conditional compilation C preprocessing -statements:** - -* `TEST_SOURCE_FILE()` -* `TEST_INCLUDE_PATH()` - -Wrapping `TEST_SOURCE_FILE()` and `TEST_INCLUDE_PATH()` in conditional -compilation statements (e.g. `#ifdef`) will not behave as you expect. These -macros are used as markers for advanced abilities discovered by Ceedling parsing -a test file as plain text. Whether or not Ceedling preprocessing is enabled, -Ceedling will always discover these marker macros in the plain text of a test file. +process them. + +That is, Ceedling’s preprocessing and these Unity features are not presently +compatible. Note that it _is_ possible to enable preprocessing for mockable +header files apart from enabling it for test files. See the documentation for +`:project` ↳ `:use_test_preprocessing`. + +**_IMPORTANT:_ The following new build directive macro `TEST_INCLUDE_PATH()` +available in Ceedling 1.0.0 is incompatible with enclosing conditional +compilation C preprocessing statements:** + +Wrapping `TEST_INCLUDE_PATH()` in conditional compilation statements +(e.g. `#ifdef`) will not behave as you expect. This macro is used as a marker +for advanced abilities discovered by Ceedling parsing a test file as plain text. +Whether or not Ceedling preprocessing is enabled, Ceedling will always discover +this marker macro in the plain text of a test file. + +Why is `TEST_INCLUDE_PATH()` incompatible with `#ifdef`? Well, it’s because of +a cyclical dependency that cannot be resolved. In order to perform test +preprocessing, we need a full complement of `#include` search paths. These +could be provided, in part, by `TEST_INCLUDE_PATH()`. But, if we allow +`TEST_INCLUDE_PATH()` to be placed within conditional compilation C +preprocessing statements, our search paths may be different after test +preprocessing! The only solution is to disallow this and scan a test file as +plain text looking for this macro at the beginning of a test build. + +**_Notes:_** + +* `TEST_SOURCE_FILE()` _can_ be placed within conditional compilation + C preprocessing statements. +* `TEST_INCLUDE_PATH()` & `TEST_SOURCE_FILE()` can be “hidden” from Ceedling’s + text scanning with traditional C comments. ### Preprocessing of your test files diff --git a/lib/ceedling/test_invoker.rb b/lib/ceedling/test_invoker.rb index fb7772ae..f0994198 100644 --- a/lib/ceedling/test_invoker.rb +++ b/lib/ceedling/test_invoker.rb @@ -91,19 +91,19 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) filepath = details[:filepath] if @configurator.project_use_test_preprocessor_tests - msg = @reportinator.generate_progress( "Parsing #{File.basename(filepath)} for build directive macros" ) + msg = @reportinator.generate_progress( "Parsing #{File.basename(filepath)} for include path build directive macros" ) @loginator.log( msg ) - # Just build directive macros using simple text scanning. + # Just build directive macro using simple text scanning. # Other context collected in later steps with help of preprocessing. @file_wrapper.open( filepath, 'r' ) do |input| - @context_extractor.collect_simple_context( filepath, input, :build_directive_source_files, :build_directive_include_paths ) + @context_extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) end else msg = @reportinator.generate_progress( "Parsing #{File.basename(filepath)} for build directive macros, #includes, and test case names" ) @loginator.log( msg ) - # Collect everything using simple text scanning (no preprocessing involve). + # Collect everything using simple text scanning (no preprocessing involved). @file_wrapper.open( filepath, 'r' ) do |input| @context_extractor.collect_simple_context( filepath, input, :all ) end @@ -111,13 +111,8 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) end - # Validate test build directive paths via TEST_INCLUDE_PATH() & augment header file collection from the same + # Validate paths via TEST_INCLUDE_PATH() & augment header file collection from the same @helper.process_project_include_paths() - - # Validate test build directive source file entries via TEST_SOURCE_FILE() - @testables.each do |_, details| - @helper.validate_build_directive_source_files( test:details[:name], filepath:details[:filepath] ) - end end # Fill out testables data structure with build context @@ -273,6 +268,17 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) # Replace default input with preprocessed file @lock.synchronize { details[:runner][:input_filepath] = filepath } + + # Collect sources added to test build with TEST_SOURCE_FILE() directive macro + # TEST_SOURCE_FILE() can be within #ifdef's--this retrieves them + @file_wrapper.open( filepath, 'r' ) do |input| + @context_extractor.collect_simple_context( filepath, input, :build_directive_source_files ) + end + + # Validate test build directive source file entries via TEST_SOURCE_FILE() + @testables.each do |_, details| + @helper.validate_build_directive_source_files( test:details[:name], filepath:details[:filepath] ) + end end } if @configurator.project_use_test_preprocessor_tests From 21bed1431380c5ac8b17ad89180a3c21233e95f3 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 00:08:31 -0500 Subject: [PATCH 23/35] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Conditional=20behavi?= =?UTF-8?q?ors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/test_context_extractor.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/ceedling/test_context_extractor.rb b/lib/ceedling/test_context_extractor.rb index 86d9d32f..d682a235 100644 --- a/lib/ceedling/test_context_extractor.rb +++ b/lib/ceedling/test_context_extractor.rb @@ -69,9 +69,9 @@ def collect_simple_context( filepath, input, *args ) end end - collect_build_directive_include_paths( filepath, include_paths ) - collect_build_directive_source_files( filepath, source_extras ) - collect_includes( filepath, includes ) + collect_build_directive_include_paths( filepath, include_paths ) if args.include?( :build_directive_include_paths ) + collect_build_directive_source_files( filepath, source_extras ) if args.include?( :build_directive_source_files ) + collect_includes( filepath, includes ) if args.include?( :includes ) # Different code processing pattern for test runner if args.include?( :test_runner_details ) @@ -359,6 +359,8 @@ def _extract_includes(line) ## def ingest_build_directive_source_files(filepath, source_extras) + return if source_extras.empty? + key = form_file_key( filepath ) @lock.synchronize do @@ -367,6 +369,8 @@ def ingest_build_directive_source_files(filepath, source_extras) end def ingest_build_directive_include_paths(filepath, include_paths) + return if include_paths.empty? + key = form_file_key( filepath ) @lock.synchronize do From 11cc78781576dfc3f8598607c8a38a53af972af9 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 00:08:46 -0500 Subject: [PATCH 24/35] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20filepath=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/test_invoker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ceedling/test_invoker.rb b/lib/ceedling/test_invoker.rb index f0994198..d388cfad 100644 --- a/lib/ceedling/test_invoker.rb +++ b/lib/ceedling/test_invoker.rb @@ -272,7 +272,7 @@ def setup_and_invoke(tests:, context:TEST_SYM, options:{}) # Collect sources added to test build with TEST_SOURCE_FILE() directive macro # TEST_SOURCE_FILE() can be within #ifdef's--this retrieves them @file_wrapper.open( filepath, 'r' ) do |input| - @context_extractor.collect_simple_context( filepath, input, :build_directive_source_files ) + @context_extractor.collect_simple_context( details[:filepath], input, :build_directive_source_files ) end # Validate test build directive source file entries via TEST_SOURCE_FILE() From 89851ed027017fdfe8801879e7ba6dcdc533246f Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 11:30:42 -0500 Subject: [PATCH 25/35] =?UTF-8?q?=E2=9C=A8=20Extract=20directives=20only?= =?UTF-8?q?=20if=20mocking=20inline=20funcs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/defaults.rb | 3 ++ lib/ceedling/preprocessinator.rb | 25 ++++++++------ lib/ceedling/preprocessinator_file_handler.rb | 33 +++++++++++++++---- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index 260ae86b..3f448835 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -398,6 +398,9 @@ # (B) Test runner generator uses these same configuration values :mock_prefix => 'Mock', :mock_suffix => '', + # CMock's default duplicated here. + # We need a value present so preprocessing logic can safely reference it. + :treat_inlines => :exclude, # Just because strict ordering is the way to go :enforce_strict_ordering => true }, diff --git a/lib/ceedling/preprocessinator.rb b/lib/ceedling/preprocessinator.rb index 5d34598b..af121cc6 100644 --- a/lib/ceedling/preprocessinator.rb +++ b/lib/ceedling/preprocessinator.rb @@ -34,7 +34,8 @@ def preprocess_includes(filepath:, test:, flags:, include_paths:, defines:) includes = [] - if @file_wrapper.newer?(includes_list_filepath, filepath) + # If existing YAML file of includes is newer than the file we're processing, skip preprocessing + if @file_wrapper.newer?( includes_list_filepath, filepath ) msg = @reportinator.generate_module_progress( operation: "Loading #include statement listing file for", module_name: test, @@ -58,6 +59,7 @@ def preprocess_includes(filepath:, test:, flags:, include_paths:, defines:) @loginator.log( msg, Verbosity::DEBUG ) @loginator.log( '', Verbosity::DEBUG ) + # Full preprocessing-based #include extraction with saving to YAML file else includes = @includes_handler.extract_includes( filepath: filepath, @@ -110,7 +112,7 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de defines: defines } - # Extract includes & print status message + # Extract includes & log progress and details includes = preprocess_file_common( **arg_hash ) arg_hash = { @@ -118,14 +120,16 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de test: test, flags: flags, include_paths: include_paths, - defines: defines + defines: defines, + extras: (@configurator.cmock_treat_inlines == :include) } + # `contents` & `extras` are arrays of text strings to be assembled in generating a new header file. + # `extras` are macro definitions, pragmas, etc. needed for the special case of mocking `inline` function declarations. + # `extras` are empty for any cases other than mocking `inline` function declarations + # (We don't want to increase our chances of a badly generated file--extracting extras could fail in complex files.) contents, extras = @file_handler.collect_header_file_contents( **arg_hash ) - # Run file through preprocessor & further process result - # plugin_arg_hash[:shell_result] = @file_handler.preprocess_header_file( **arg_hash ) - arg_hash = { filename: File.basename( filepath ), preprocessed_filepath: preprocessed_filepath, @@ -134,6 +138,7 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de includes: includes } + # Create a reconstituted header file from preprocessing expansion and preserving any extras @file_handler.assemble_preprocessed_header_file( **arg_hash ) # Trigger post_mock_preprocessing plugin hook @@ -166,7 +171,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:) defines: defines } - # Extract includes & print status message + # Extract includes & log progress and info includes = preprocess_file_common( **arg_hash ) arg_hash = { @@ -177,11 +182,10 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:) defines: defines } + # `contents` & `extras` are arrays of text strings to be assembled in generating a new test file. + # `extras` are test build directives TEST_SOURCE_FILE() and TEST_INCLUDE_PATH(). contents, extras = @file_handler.collect_test_file_contents( **arg_hash ) - # Run file through preprocessor & further process result - # plugin_arg_hash[:shell_result] = @file_handler.preprocess_test_file( **arg_hash ) - arg_hash = { filename: File.basename( filepath ), preprocessed_filepath: preprocessed_filepath, @@ -190,6 +194,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:) includes: includes } + # Create a reconstituted test file from preprocessing expansion and preserving any extras @file_handler.assemble_preprocessed_test_file( **arg_hash ) # Trigger pre_mock_preprocessing plugin hook diff --git a/lib/ceedling/preprocessinator_file_handler.rb b/lib/ceedling/preprocessinator_file_handler.rb index 0b8a1623..b2c15f63 100644 --- a/lib/ceedling/preprocessinator_file_handler.rb +++ b/lib/ceedling/preprocessinator_file_handler.rb @@ -11,13 +11,17 @@ class PreprocessinatorFileHandler constructor :preprocessinator_extractor, :configurator, :tool_executor, :file_path_utils, :file_wrapper, :loginator - def collect_header_file_contents(source_filepath:, test:, flags:, defines:, include_paths:) + def collect_header_file_contents(source_filepath:, test:, flags:, defines:, include_paths:, extras:) contents = [] + + # Our extra file content to be preserved + # Leave these empty if :extras is false pragmas = [] macro_defs = [] preprocessed_filepath = @file_path_utils.form_preprocessed_file_full_expansion_filepath( source_filepath, test ) + # Run GCC with full preprocessor expansion command = @tool_executor.build_command_line( @configurator.tools_test_file_full_preprocessor, flags, @@ -25,16 +29,19 @@ def collect_header_file_contents(source_filepath:, test:, flags:, defines:, incl preprocessed_filepath, defines, include_paths - ) - + ) @tool_executor.exec( command ) @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) end + # Bail out, skipping directives-only preprocessing if no extras are required + return contents, (pragmas + macro_defs) if !extras + preprocessed_filepath = @file_path_utils.form_preprocessed_file_directives_only_filepath( source_filepath, test ) + # Run GCC with directives-only preprocessor expansion command = @tool_executor.build_command_line( @configurator.tools_test_file_directives_only_preprocessor, flags, @@ -43,14 +50,22 @@ def collect_header_file_contents(source_filepath:, test:, flags:, defines:, incl defines, include_paths ) - @tool_executor.exec( command ) + # Try to find an #include guard in the first 2k of the file text. + # An #include guard is one macro from the original file we don't want to preserve if we can help it. + # We create our own #include guard in the header file we create. + # It's possible preserving the macro from the original file's #include guard could trip something up. + # Of course, it's also possible some header conditional compilation feature is dependent on it. + # ¯\_(ツ)_/¯ include_guard = @preprocessinator_extractor.extract_include_guard( @file_wrapper.read( source_filepath, 2048 ) ) @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + # Get code contents of preprocessed directives-only file as a string + # TODO: Modify to process line-at-a-time for memory savings & performance boost _contents = @preprocessinator_extractor.extract_file_as_string_from_expansion( file, preprocessed_filepath ) + # Extract pragmas and macros from pragmas = @preprocessinator_extractor.extract_pragmas( _contents ) macro_defs = @preprocessinator_extractor.extract_macro_defs( _contents, include_guard ) end @@ -127,10 +142,12 @@ def assemble_preprocessed_header_file(filename:, preprocessed_filepath:, content def collect_test_file_contents(source_filepath:, test:, flags:, defines:, include_paths:) contents = [] + # TEST_SOURCE_FILE() and TEST_INCLUDE_PATH() test_directives = [] preprocessed_filepath = @file_path_utils.form_preprocessed_file_full_expansion_filepath( source_filepath, test ) + # Run GCC with full preprocessor expansion command = @tool_executor.build_command_line( @configurator.tools_test_file_full_preprocessor, flags, @@ -139,7 +156,6 @@ def collect_test_file_contents(source_filepath:, test:, flags:, defines:, includ defines, include_paths ) - @tool_executor.exec( command ) @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| @@ -148,6 +164,7 @@ def collect_test_file_contents(source_filepath:, test:, flags:, defines:, includ preprocessed_filepath = @file_path_utils.form_preprocessed_file_directives_only_filepath( source_filepath, test ) + # Run GCC with directives-only preprocessor expansion command = @tool_executor.build_command_line( @configurator.tools_test_file_directives_only_preprocessor, flags, @@ -155,13 +172,15 @@ def collect_test_file_contents(source_filepath:, test:, flags:, defines:, includ preprocessed_filepath, defines, include_paths - ) - + ) @tool_executor.exec( command ) @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + # Get code contents of preprocessed directives-only file as a string + # TODO: Modify to process line-at-a-time for memory savings & performance boost _contents = @preprocessinator_extractor.extract_file_as_string_from_expansion( file, preprocessed_filepath ) + # Extract TEST_SOURCE_FILE() and TEST_INCLUDE_PATH() test_directives = @preprocessinator_extractor.extract_test_directive_macro_calls( _contents ) end From 4dafe94b0a709937090440cdf918efb89b3f0a88 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 11:31:03 -0500 Subject: [PATCH 26/35] =?UTF-8?q?=F0=9F=93=9D=20Added=20documentation=20co?= =?UTF-8?q?mments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ceedling/preprocessinator_extractor.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index 9ec6397d..6bb48f03 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -194,6 +194,11 @@ def extract_macro_defs(file_contents, include_guard) def extract_multiline_directives(file_contents, directive) results = [] + # Output from the GCC preprocessor directives-only mode is the intended input to be processed here. + # The GCC preprpocessor smooshes multiline directives into a single line. + # We process both single and multiline directives here in case this is ever not true or we need + # to extract directives from files that have not been preprocessed. + # This regex captures any single or multiline preprocessor directive definition: # - Looks for any string that begins with '#' ('#' and '' may be separated by spaces per C spec). # - Captures all text (non-greedily) after '#' on a first line through 0 or more line continuations up to a final newline. From fa80d1e9dff6d0ef344108e82136828996721202 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 12:06:56 -0500 Subject: [PATCH 27/35] =?UTF-8?q?=F0=9F=93=9D=20Documentation=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated preprocessing related to `TEST_SOURCE_FILE()`, `TEST_INCLUDE_PATH()`, `TEST_CASE()`, and `TEST_RANGE()` --- README.md | 7 ++++++- docs/BreakingChanges.md | 14 ++++++-------- docs/CeedlingPacket.md | 14 ++++++++------ docs/Changelog.md | 11 ++++++----- docs/PluginDevelopmentGuide.md | 8 +++++++- docs/ReleaseNotes.md | 7 ++++--- 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index faedc7ec..52fc9248 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Ceedling ![CI](https://github.com/ThrowTheSwitch/Ceedling/workflows/CI/badge.svg) ======== -_October 29, 2024_ 🚚 **Ceedling 1.0.0** is a release candidate and will be +_November 28, 2024_ 🚚 **Ceedling 1.0.0** is a release candidate and will be shipping very soon. See the [Release Notes](docs/ReleaseNotes.md) for an overview of all that’s new since 0.31.1 plus links to the detailed Changelog and list of Breaking Changes. @@ -334,6 +334,11 @@ Training and support contracts are available through **_[Ceedling Pro][ceedling- [TTS-help]: https://www.throwtheswitch.org/#help-section +The [Agile Embedded Podcast][ae-podcast] includes an [episode on Ceedling][ceedling-episode]! + +[ae-podcast]: https://agileembeddedpodcast.com/ +[ceedling-episode]: https://agileembeddedpodcast.com/episodes/ceedling + ## Ceedling docs * **_[Ceedling Packet][ceedling-packet]_** is Ceedling’s user manual. It also references and links to the documentation of the projects, _Unity_, _CMock_, and _CException_, that it weaves together into your test and release builds. diff --git a/docs/BreakingChanges.md b/docs/BreakingChanges.md index 36ad02a9..13ad8c7d 100644 --- a/docs/BreakingChanges.md +++ b/docs/BreakingChanges.md @@ -7,7 +7,7 @@ These breaking changes are complemented by two other documents: --- -# [1.0.0 pre-release] — 2024-11-15 +# [1.0.0 pre-release] — 2024-11-28 ## Explicit `:paths` ↳ `:include` entries in the project file @@ -60,17 +60,15 @@ In place of `true` or `false`, `:use_test_preprocessing` now accepts: The previously undocumented `TEST_FILE()` build directive macro (#796) available within test files has been renamed and is now officially documented. -## Preprocessing is temporarily unable to handle `TEST_CASE()`, `TEST_RANGE()`, `TEST_SOURCE_FILE()`, and `TEST_INCLUDE_PATH()` +## Preprocessing is unable to handle Unity’s `TEST_CASE()` and `TEST_RANGE()` and has limits for `TEST_INCLUDE_PATH()` -Ceedling’s preprocessing abilities have been nearly entirely rewritten. In the process of doing so Ceedling has temporarily lost the ability to preprocess a test file but properly handle directive macros including Unity’s parameterized test case macros and test file build diretive macros. +Ceedling’s preprocessing abilities have been nearly entirely rewritten. The one case Ceedling cannot yet handle is preprocessing test files that contain Unity’s parameterized test case macros. -`TEST_CASE()` and `TEST_RANGE()` are Unity macros that effectively disappear when Ceedling uses the GNU preprocessor to expand a test file into raw code to extract details with text parsing. After preprocessing, these macros no longer exist in the test file that is then compiled. +`TEST_CASE()` and `TEST_RANGE()` are Unity macros that are positional in a file in relation to the test case functions they modify. While Ceedling's test preprocessing can preserve these macro calls, their position cannot be preserved. -You may have need to wrap `TEST_SOURCE_FILE()` and `TEST_INCLUDE_PATH()` in conditional compilation preprocessor statements (e.g. `#ifdef`). This will not work as you expect. These macros are used as markers for advanced abilities discovered by Ceedling parsing a test file as plain text. Whether or not Ceedling preprocessing is enabled, Ceedling will always discover these marker macros in the plain text of a test file. +You may want to wrap `TEST_INCLUDE_PATH()` in conditional compilation preprocessor statements (e.g. `#ifdef`). This will not work as you expect. This macro “marker” must be discovered at the beginning of a test build by Ceedling parsing a test file as plain text. Cyclical dependencies related to preprocessing prevent anything more sophisticated. -In future revisions of Ceedling, support for these macros in preprocessing scenarios will be brought back (very likely without a dedicated configuration option — hopefully, we’ll get it to just work). - -Note: `:project` ↳ `:use_test_preprocessor` is no longer a binary setting (`true`/`false`). Mockable header file preprocessing can now be enabled with a `:mocks` setting while test files are left untouched by preprocessing. This should support the majority of advanced use cases for preprocessing. +Note: `:project` ↳ `:use_test_preprocessor` is no longer a binary setting (`true`/`false`). Mockable header file preprocessing can now be enabled with a `:mocks` setting while test files are left untouched by preprocessing. This can allow test preprocessing in the common cases of sophtisticate mockable headers while Unity’s `TEST_CASE()` and `TEST_RANGE()` are utilized in a test file untouched by preprocessing. ## Quoted executables in tool definitions diff --git a/docs/CeedlingPacket.md b/docs/CeedlingPacket.md index 54c16e84..b7c9d0ff 100644 --- a/docs/CeedlingPacket.md +++ b/docs/CeedlingPacket.md @@ -1538,20 +1538,22 @@ The sections that follow flesh out the details of the bulleted list above. #### Preprocessing gotchas -**_NOTE:_ As of Ceedling 1.0.0, Ceedling’s preprocessing feature has a -limitation that affects Unity features triggered by the following macros:** +**_IMPORTANT:_ As of Ceedling 1.0.0, Ceedling’s test preprocessing feature +has a limitation that affects Unity features triggered by the following macros.** * `TEST_CASE()` * `TEST_RANGE()` -With Ceedling’s preprocessing enabled, Unity’s `TEST_CASE()` and `TEST_RANGE()` -in your test files will effectively vanish before test compilation is able to -process them. +`TEST_CASE()` and `TEST_RANGE()` are Unity macros that are positional in a file +in relation to the test case functions they modify. While Ceedling's test file +preprocessing can preserve these macro calls, their position cannot be preserved. That is, Ceedling’s preprocessing and these Unity features are not presently compatible. Note that it _is_ possible to enable preprocessing for mockable header files apart from enabling it for test files. See the documentation for -`:project` ↳ `:use_test_preprocessing`. +`:project` ↳ `:use_test_preprocessing`. This can allow test preprocessing in the +common cases of sophtisticate mockable headers while Unity’s `TEST_CASE()` and +`TEST_RANGE()` are utilized in a test file untouched by preprocessing. **_IMPORTANT:_ The following new build directive macro `TEST_INCLUDE_PATH()` available in Ceedling 1.0.0 is incompatible with enclosing conditional diff --git a/docs/Changelog.md b/docs/Changelog.md index 4056b530..8ed5e278 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -9,7 +9,7 @@ This changelog is complemented by two other documents: --- -# [1.0.0 pre-release] — 2024-11-15 +# [1.0.0 pre-release] — 2024-11-28 ## 🌟 Added @@ -33,22 +33,23 @@ See the _[Release Notes](ReleaseNotes.md)_ and _[CeedlingPacket](CeedlingPacket. Issue [#743](https://github.com/ThrowTheSwitch/Ceedling/issues/743) -Using what we are calling build directive macros, you can now provide Ceedling certain configuration details from inside a test file. +Using what we are calling test build directive macros, you can now provide Ceedling certain configuration details from inside a test file. See the [documentation](CeedlingPacket.md) discussion on include paths, Ceedling conventions, and these macros to understand all the details. _Notes:_ -* Ceedling is not yet capable of preserving build directive macros through preprocessing of test files. If, for example, you wrap these macros within conditional compilation C preprocessing statements (e.g. `#ifdef`), they will not work as you expect. Specifically, because these macros act as simple markers and are discovered by plain text parsing, Ceedling 1.0.0 will always discover them regardless of conditional compilation wrappers or Ceedling’s preprocessing configuration. +* Ceedling can preserves these test build directive macros through preprocessing of test files. However, wrapping `TEST_INCLUDE_PATH()` in conditional compilation C preprocessing statements (e.g. `#ifdef`) can produce unexpected resutls (see documentation for more). +* Both `TEST_INCLUDE_PATH()` & `TEST_SOURCE_FILE()` can be disabled with standard C comments. * However, preprocessing of mockable header files can now be enabled separately (see `:project` ↳ `:use_test_preprocessor`). #### `TEST_INCLUDE_PATH(...)` -In short, `TEST_INCLUDE_PATH()` allows you to add a header file search path to the build of the test executable in which it is found. This can mean much shorter compilation command lines and good flexibility for complicated projects. +In short, `TEST_INCLUDE_PATH()` allows you to add a header file search path to the build of the test executable in which it is found. This can mean much shorter compilation command lines and good flexibility for complicated projects. Unlike , `TEST_SOURCE_FILE()` this should _**not**_ be wrapped in conditional compilation preprocessing statements (see documentation for more). #### `TEST_SOURCE_FILE(...)` -In short, `TEST_SOURCE_FILE()` allows you to be explicit as to which source C files should be compiled and linked into a test executable. Sometimes Ceedling’s convention for matching source files with test files by way of `#include`d header files does not meet the need. This solves the problems of those scenarios. +In short, `TEST_SOURCE_FILE()` allows you to be explicit as to which source C files should be compiled and linked into a test executable. Sometimes Ceedling’s convention for matching source files with test files by way of `#include`d header files does not meet the need. This solves the problems of those scenarios. Unlike `TEST_INCLUDE_PATH()` , this test build directive _can_ be wrapped in in conditional compilation preprocessing statements (see documentation for more). ### Mixins for modifying your configuration diff --git a/docs/PluginDevelopmentGuide.md b/docs/PluginDevelopmentGuide.md index 1dad1dc0..64565ad9 100644 --- a/docs/PluginDevelopmentGuide.md +++ b/docs/PluginDevelopmentGuide.md @@ -20,7 +20,7 @@ Ceedling plugins or by simply searching for code examples online. ## Development Roadmap & Notes -_February 19, 2024_ +_November 28, 2024_ (See Ceedling's _[release notes](ReleaseNotes.md)_ for more.) @@ -371,6 +371,12 @@ whose associated value is itself a hash with the following contents: } ``` +_**Note:**_ Test preprocessing steps are quite sophissticated and involve various +combination of tool executions. The `post_` preprocessing hooks do not inlucde shell +results. Future updates to Ceedling’s plugin system will create a more robust means +of attaching custom behaviors to test preprocessing or connecting your own preprocessing +pipeline with toolchains other than GCC. + ## `Plugin` hook methods `pre_mock_preprocess(arg_hash)` and `post_mock_preprocess(arg_hash)` These methods are called before and after execution of preprocessing for header diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index e6dcbcba..8eebe9ff 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -7,7 +7,7 @@ These release notes are complemented by two other documents: --- -# 1.0.0 pre-release — October 31, 2024 +# 1.0.0 pre-release — November 28, 2024 **This Ceedling release is probably the most significant since the project was first [posted to SourceForge in 2009][sourceforge].** @@ -267,7 +267,7 @@ The previously undocumented build directive macro `TEST_FILE(...)` has been rena #### Preprocessing improvements -Ceedling has been around for a number of years and has had the benefit of many contributors over that time. Preprocessing (expanding macros in test files and header files to be mocked) is quite tricky to get right but is essential for big, complicated test suites. Over Ceedling’s long life various patches and incremental improvements have evolved in such a way that preprocessing had become quite complicated and often did the wrong thing. Much of this has been fixed and improved in this release (some of it has been temporarily removed). +Ceedling has been around for a number of years and has had the benefit of many contributors over that time. Preprocessing (e.g. expanding macros in test files and header files to be mocked) is quite tricky to get right but is essential for big, complicated test suites. Over Ceedling’s long life various patches and incremental improvements have evolved in such a way that preprocessing had become quite complicated and often did the wrong thing. Much of this has been fixed and improved in this release. Considerable memory and performance improvements have been made as well. #### Test Suite Crash Handling @@ -316,9 +316,10 @@ Together, these changes may cause you to think that Ceedling is running steps ou ## 🩼 Known Issues -1. The new internal pipeline that allows builds to be parallelized and configured per-test-executable can mean a fair amount of duplication of steps. A header file may be mocked identically multiple times. The same source file may be compiled identically multiple times. The speed gains due to parallelization more than make up for this. Future releases will concentrate on optimizing away duplication of build steps. +1. The new internal pipeline that allows builds to be parallelized and configured per-test-executable can mean a fair amount of duplication of steps. A header file may be mocked identically multiple times. The same source file may be compiled identically multiple times. The speed gains due to parallelization help make up for this. Future releases will concentrate on optimizing away duplication of build steps. 1. While header file search paths are now customizable per executable, this currently only applies to the search paths the compiler uses. Distinguishing test files or header files of the same name in different directories for test runner and mock generation respectively continues to rely on educated guesses in Ceedling code. 1. Any path for a C file specified with `TEST_SOURCE_FILE(...)` is in relation to **_project root_** — that is, from where you execute `ceedling` at the command line. If you move source files or change your directory structure, many of your `TEST_SOURCE_FILE(...)` calls may need to be updated. A more flexible and dynamic approach to path handling will come in a future update. +1. Ceedling’s many test preprocessing improvements are not presently able to preserve Unity’s special `TEST_CASE()` and `TEST_RANGE()` features. However, preprocessing of test files is much less frequently needed than preprocessing of mockable header files. Test preprocessing can now be configured to enable only one or the other. As such, these advanced Unity features can still be used in even sophisticated projects. ## 📚 Background Knowledge From 3d5680a195f18c958b3b2d43217fee5c46bad36f Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 13:05:52 -0500 Subject: [PATCH 28/35] =?UTF-8?q?=F0=9F=90=9B=20Setting=20config=20for=20i?= =?UTF-8?q?ncomplete=20:unity=20&=20:cmock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The order of revised defaults and automatic configuration settings handling left the possiblilty of trying to set a value in Unity and CMock configuration that was incomplete. A user’s configuration might create a partial configuration block that automatic settings tried to fill out but would encounter nil references dpeending on the accompanying logic. The fix was to reorder how user config, defaults, and auto configuration values are processed. --- lib/ceedling/configurator.rb | 41 +++++++++++++++++++----------------- lib/ceedling/setupinator.rb | 19 ++++++----------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index 85c3b0d7..5671da3e 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -187,28 +187,38 @@ def merge_ceedling_runtime_config(config, runtime_config) config.deep_merge( runtime_config ) end + def populate_with_defaults( config_hash, defaults_hash ) + msg = @reportinator.generate_progress( 'Populating project configuration with collected default values' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + @configurator_builder.populate_with_defaults( config_hash, defaults_hash ) + end + def populate_unity_config(config) msg = @reportinator.generate_progress( 'Processing Unity configuration' ) @loginator.log( msg, Verbosity::OBNOXIOUS ) - # :unity is not guaranteed to exist in a user configuration before populating it. - if config[:unity][:use_param_tests] config[:unity][:defines] << 'UNITY_SUPPORT_TEST_CASES' config[:unity][:defines] << 'UNITY_SUPPORT_VARIADIC_MACROS' end + + @loginator.log( "Unity configuration >> #{config[:unity]}", Verbosity::DEBUG ) end def populate_cmock_config(config) - # :unity is not guaranteed to exist in a user configuration before populating it. + # Save CMock config reference + @cmock_config = config[:cmock] - # Populate config with CMock config - cmock = config[:cmock] || {} - @cmock_config = cmock + cmock = config[:cmock] - return if !config[:project][:use_mocks] + # Do no more prep if we're not using mocks + if !config[:project][:use_mocks] + @loginator.log( "CMock configuration >> #{cmock}", Verbosity::DEBUG ) + return + end msg = @reportinator.generate_progress( 'Processing CMock configuration' ) @loginator.log( msg, Verbosity::OBNOXIOUS ) @@ -217,11 +227,6 @@ def populate_cmock_config(config) cmock[:plugins].map! { |plugin| plugin.to_sym() } cmock[:plugins].uniq! - # CMock includes safe defaults - cmock[:includes] = [] if (cmock[:includes].nil?) - - # Default to empty array if cmock[:unity_helper_path] not provided - cmock[:unity_helper_path] = [] if cmock[:unity_helper_path].nil? # Reformulate CMock helper path value as array of one element if it's a string in config cmock[:unity_helper_path] = [cmock[:unity_helper_path]] if cmock[:unity_helper_path].is_a?( String ) @@ -231,14 +236,8 @@ def populate_cmock_config(config) end cmock[:includes].uniq! - end - - - def populate_with_defaults( config_hash, defaults_hash ) - msg = @reportinator.generate_progress( 'Populating project configuration with collected default values' ) - @loginator.log( msg, Verbosity::OBNOXIOUS ) - @configurator_builder.populate_with_defaults( config_hash, defaults_hash ) + @loginator.log( "CMock configuration >> #{cmock}", Verbosity::DEBUG ) end @@ -263,6 +262,8 @@ def populate_test_runner_generation_config(config) config[:test_runner][:use_param_tests] = config[:unity][:use_param_tests] @runner_config = config[:test_runner] + + @loginator.log( "Test Runner configuration >> #{config[:test_runner]}", Verbosity::DEBUG ) end @@ -274,6 +275,8 @@ def populate_exceptions_config(config) config[:project][:use_exceptions] = true end + + @loginator.log( "CException configuration >> #{config[:cexception]}", Verbosity::DEBUG ) end diff --git a/lib/ceedling/setupinator.rb b/lib/ceedling/setupinator.rb index 56cdde1b..8e954ddc 100644 --- a/lib/ceedling/setupinator.rb +++ b/lib/ceedling/setupinator.rb @@ -69,7 +69,7 @@ def do_setup( app_cfg ) ## 2. Handle basic configuration ## - log_step( 'Project Configuration Handling' ) + log_step( 'Base configuration handling', heading:false ) # Evaluate environment vars before plugin configurations that might reference with inline Ruby string expansion @configurator.eval_environment_variables( config_hash ) @@ -77,12 +77,6 @@ def do_setup( app_cfg ) # Standardize paths and add to Ruby load paths plugins_paths_hash = @configurator.prepare_plugins_load_paths( app_cfg[:ceedling_plugins_path], config_hash ) - # Populate Unity configuration with values to tie vendor tool configurations together - @configurator.populate_unity_config( config_hash ) - - # Populate CMock configuration with values to tie vendor tool configurations together - @configurator.populate_cmock_config( config_hash ) - ## ## 3. Plugin Handling ## @@ -115,14 +109,15 @@ def do_setup( app_cfg ) log_step( 'Completing Project Configuration' ) + # Populate Unity configuration with values to tie vendor tool configurations together + @configurator.populate_unity_config( config_hash ) + + # Populate CMock configuration with values to tie vendor tool configurations together + @configurator.populate_cmock_config( config_hash ) + # Configure test runner generation @configurator.populate_test_runner_generation_config( config_hash ) - @loginator.log( "Unity configuration >> #{config_hash[:unity]}", Verbosity::DEBUG ) - @loginator.log( "CMock configuration >> #{config_hash[:cmock]}", Verbosity::DEBUG ) - @loginator.log( "Test Runner configuration >> #{config_hash[:test_runner]}", Verbosity::DEBUG ) - @loginator.log( "CException configuration >> #{config_hash[:cexception]}", Verbosity::DEBUG ) - # Automagically enable use of exceptions based on CMock settings @configurator.populate_exceptions_config( config_hash ) From 4b0fdb56ad01077be28065cfe4a084039b3bc0ac Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 13:11:23 -0500 Subject: [PATCH 29/35] =?UTF-8?q?=E2=9C=85=20More=20robust=20&=20simpler?= =?UTF-8?q?=20unit=20test=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A previous refactoring provided a better wrapper method to access line-at-a-time code cleaning. --- lib/ceedling/test_context_extractor.rb | 92 +++++++++++++------------- spec/test_context_extractor_spec.rb | 14 ++-- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/lib/ceedling/test_context_extractor.rb b/lib/ceedling/test_context_extractor.rb index d682a235..b21c96ff 100644 --- a/lib/ceedling/test_context_extractor.rb +++ b/lib/ceedling/test_context_extractor.rb @@ -234,45 +234,14 @@ def ingest_includes(filepath, includes) end end - # Exposed for testing (called from private `code_lines()`) - def clean_code_line(line, comment_block) - _line = sanitize_encoding( line ) - - # 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 + # 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 ################################# @@ -401,13 +370,44 @@ def form_file_key( filepath ) return filepath.to_s.to_sym end - 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 + def clean_code_line(line, comment_block) + _line = sanitize_encoding( line ) + + # 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 # Note: This method modifies encoding in place (encode!) in an attempt to reduce long string copies diff --git a/spec/test_context_extractor_spec.rb b/spec/test_context_extractor_spec.rb index 07caaf5e..38c6aef4 100644 --- a/spec/test_context_extractor_spec.rb +++ b/spec/test_context_extractor_spec.rb @@ -94,8 +94,8 @@ end end - context "#clean_code_line" do - it "should clean code of encoding problems and comments a line at a time" do + 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 @@ -109,16 +109,12 @@ CONTENTS got = [] - comment_block = false - file_contents.split( "\n" ).each do |line| - _line, comment_block = @extractor.clean_code_line( line, comment_block ) - _line.strip! - got << _line if !_line.empty? + @extractor.code_lines( StringIO.new( file_contents ) ) do |line| + line.strip! + got << line if !line.empty? end - got.compact! - expected = [ 'Some text', # ⛔️ removed with encoding sanitizing 'More text', From 5be498dec9f90568366c9cc916bd8c43f0a7fd27 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 13:43:08 -0500 Subject: [PATCH 30/35] =?UTF-8?q?=F0=9F=92=9A=20Fixed=20Thor=20gem=20versi?= =?UTF-8?q?on=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ceedling.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceedling.gemspec b/ceedling.gemspec index de0ce48b..3b4bc22f 100644 --- a/ceedling.gemspec +++ b/ceedling.gemspec @@ -39,7 +39,7 @@ Ceedling projects are created with a YAML configuration file. A variety of conve s.required_ruby_version = ">= 3.0.0" - s.add_dependency "thor", ">= 0.14" + s.add_dependency "thor", "~> 1.3" s.add_dependency "rake", ">= 12", "< 14" s.add_dependency "deep_merge", "~> 1.2" s.add_dependency "constructor", "~> 2" From 6061ccfc3a68015673f2e36d702f893e839234b9 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 13:58:33 -0500 Subject: [PATCH 31/35] =?UTF-8?q?=F0=9F=94=A5=20Removed=20unused=20vendore?= =?UTF-8?q?d=20behaviors=20&=20hardmock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/test_environment.rb | 2 - vendor/behaviors/Manifest.txt | 9 - vendor/behaviors/Rakefile | 19 - vendor/behaviors/lib/behaviors.rb | 84 --- vendor/behaviors/lib/behaviors/reporttask.rb | 166 ------ vendor/behaviors/test/behaviors_tasks_test.rb | 81 --- vendor/behaviors/test/behaviors_test.rb | 58 --- vendor/behaviors/test/tasks_test/Rakefile | 19 - vendor/behaviors/test/tasks_test/lib/user.rb | 10 - .../test/tasks_test/test/user_test.rb | 25 - vendor/hardmock/CHANGES | 78 --- vendor/hardmock/LICENSE | 7 - vendor/hardmock/README | 70 --- vendor/hardmock/Rakefile | 8 - vendor/hardmock/config/environment.rb | 20 - vendor/hardmock/lib/assert_error.rb | 31 -- vendor/hardmock/lib/extend_test_unit.rb | 22 - vendor/hardmock/lib/hardmock.rb | 94 ---- vendor/hardmock/lib/hardmock/errors.rb | 30 -- vendor/hardmock/lib/hardmock/expectation.rb | 237 --------- .../lib/hardmock/expectation_builder.rb | 17 - vendor/hardmock/lib/hardmock/expector.rb | 34 -- .../hardmock/lib/hardmock/method_cleanout.rb | 41 -- vendor/hardmock/lib/hardmock/mock.rb | 188 ------- vendor/hardmock/lib/hardmock/mock_control.rb | 61 --- vendor/hardmock/lib/hardmock/stubbing.rb | 218 -------- vendor/hardmock/lib/hardmock/trapper.rb | 39 -- vendor/hardmock/lib/hardmock/utils.rb | 17 - vendor/hardmock/lib/test_unit_before_after.rb | 177 ------- vendor/hardmock/rake_tasks/rdoc.rake | 27 - vendor/hardmock/rake_tasks/rdoc_options.rb | 12 - vendor/hardmock/rake_tasks/test.rake | 30 -- .../test/functional/assert_error_test.rb | 60 --- .../test/functional/auto_verify_test.rb | 186 ------- .../test/functional/direct_mock_usage_test.rb | 404 --------------- .../hardmock/test/functional/hardmock_test.rb | 442 ---------------- .../hardmock/test/functional/stubbing_test.rb | 487 ------------------ vendor/hardmock/test/test_helper.rb | 51 -- .../test/unit/expectation_builder_test.rb | 27 - vendor/hardmock/test/unit/expectation_test.rb | 380 -------------- vendor/hardmock/test/unit/expector_test.rb | 65 --- .../test/unit/method_cleanout_test.rb | 44 -- .../hardmock/test/unit/mock_control_test.rb | 183 ------- vendor/hardmock/test/unit/mock_test.rb | 287 ----------- .../test/unit/test_unit_before_after_test.rb | 460 ----------------- vendor/hardmock/test/unit/trapper_test.rb | 70 --- .../hardmock/test/unit/verify_error_test.rb | 48 -- 47 files changed, 5125 deletions(-) delete mode 100644 vendor/behaviors/Manifest.txt delete mode 100644 vendor/behaviors/Rakefile delete mode 100644 vendor/behaviors/lib/behaviors.rb delete mode 100644 vendor/behaviors/lib/behaviors/reporttask.rb delete mode 100644 vendor/behaviors/test/behaviors_tasks_test.rb delete mode 100644 vendor/behaviors/test/behaviors_test.rb delete mode 100644 vendor/behaviors/test/tasks_test/Rakefile delete mode 100644 vendor/behaviors/test/tasks_test/lib/user.rb delete mode 100644 vendor/behaviors/test/tasks_test/test/user_test.rb delete mode 100644 vendor/hardmock/CHANGES delete mode 100644 vendor/hardmock/LICENSE delete mode 100644 vendor/hardmock/README delete mode 100644 vendor/hardmock/Rakefile delete mode 100644 vendor/hardmock/config/environment.rb delete mode 100644 vendor/hardmock/lib/assert_error.rb delete mode 100644 vendor/hardmock/lib/extend_test_unit.rb delete mode 100644 vendor/hardmock/lib/hardmock.rb delete mode 100644 vendor/hardmock/lib/hardmock/errors.rb delete mode 100644 vendor/hardmock/lib/hardmock/expectation.rb delete mode 100644 vendor/hardmock/lib/hardmock/expectation_builder.rb delete mode 100644 vendor/hardmock/lib/hardmock/expector.rb delete mode 100644 vendor/hardmock/lib/hardmock/method_cleanout.rb delete mode 100644 vendor/hardmock/lib/hardmock/mock.rb delete mode 100644 vendor/hardmock/lib/hardmock/mock_control.rb delete mode 100644 vendor/hardmock/lib/hardmock/stubbing.rb delete mode 100644 vendor/hardmock/lib/hardmock/trapper.rb delete mode 100644 vendor/hardmock/lib/hardmock/utils.rb delete mode 100644 vendor/hardmock/lib/test_unit_before_after.rb delete mode 100644 vendor/hardmock/rake_tasks/rdoc.rake delete mode 100644 vendor/hardmock/rake_tasks/rdoc_options.rb delete mode 100644 vendor/hardmock/rake_tasks/test.rake delete mode 100644 vendor/hardmock/test/functional/assert_error_test.rb delete mode 100644 vendor/hardmock/test/functional/auto_verify_test.rb delete mode 100644 vendor/hardmock/test/functional/direct_mock_usage_test.rb delete mode 100644 vendor/hardmock/test/functional/hardmock_test.rb delete mode 100644 vendor/hardmock/test/functional/stubbing_test.rb delete mode 100644 vendor/hardmock/test/test_helper.rb delete mode 100644 vendor/hardmock/test/unit/expectation_builder_test.rb delete mode 100644 vendor/hardmock/test/unit/expectation_test.rb delete mode 100644 vendor/hardmock/test/unit/expector_test.rb delete mode 100644 vendor/hardmock/test/unit/method_cleanout_test.rb delete mode 100644 vendor/hardmock/test/unit/mock_control_test.rb delete mode 100644 vendor/hardmock/test/unit/mock_test.rb delete mode 100644 vendor/hardmock/test/unit/test_unit_before_after_test.rb delete mode 100644 vendor/hardmock/test/unit/trapper_test.rb delete mode 100644 vendor/hardmock/test/unit/verify_error_test.rb diff --git a/config/test_environment.rb b/config/test_environment.rb index affaacce..b2572abc 100644 --- a/config/test_environment.rb +++ b/config/test_environment.rb @@ -9,8 +9,6 @@ [ 'lib', 'test', - 'vendor/behaviors/lib', - 'vendor/hardmock/lib', ].each do |dir| $LOAD_PATH.unshift( File.join( File.expand_path(File.dirname(__FILE__) + "/../"), dir) ) end diff --git a/vendor/behaviors/Manifest.txt b/vendor/behaviors/Manifest.txt deleted file mode 100644 index 6c954ecc..00000000 --- a/vendor/behaviors/Manifest.txt +++ /dev/null @@ -1,9 +0,0 @@ -Manifest.txt -Rakefile -lib/behaviors.rb -lib/behaviors/reporttask.rb -test/behaviors_tasks_test.rb -test/behaviors_test.rb -test/tasks_test/lib/user.rb -test/tasks_test/Rakefile -test/tasks_test/test/user_test.rb diff --git a/vendor/behaviors/Rakefile b/vendor/behaviors/Rakefile deleted file mode 100644 index d4d68b99..00000000 --- a/vendor/behaviors/Rakefile +++ /dev/null @@ -1,19 +0,0 @@ -require 'rake' -require 'rubygems' -require 'hoe' - -Hoe.new('behaviors','1.0.3') do |p| - p.author = "Atomic Object LLC" - p.email = "dev@atomicobject.com" - p.url = "http://behaviors.rubyforge.org" - p.summary = "behavior-driven unit test helper" - p.description = <<-EOS -Behaviors allows for Test::Unit test case methods to be defined as -human-readable descriptions of program behavior. It also provides -Rake tasks to list the behaviors of your project. - EOS - p.test_globs = ['test/*_test.rb'] - - p.changes = <<-EOS - EOS -end diff --git a/vendor/behaviors/lib/behaviors.rb b/vendor/behaviors/lib/behaviors.rb deleted file mode 100644 index 1d89e9cb..00000000 --- a/vendor/behaviors/lib/behaviors.rb +++ /dev/null @@ -1,84 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -=begin rdoc -= Usage -Behaviors provides a single method: should. - -Instead of naming test methods like: - - def test_something - end - -You declare test methods like: - - should "perform action" do - end - -You may omit the body of a should method to describe unimplemented behavior. - - should "perform other action" - -When you run your unit tests, empty should methods will appear as an 'UNIMPLEMENTED CASE' along with the described behavior. -This is useful for sketching out planned behavior quickly. - -Simply extend Behaviors in your TestCase to start using behaviors. - - require 'test/unit' - require 'behaviors' - require 'user' - - class UserTest < Test::Unit::TestCase - extend Behaviors - ... - end - -= Motivation -Test methods typically focus on the name of the method under test instead of its behavior. -Creating test methods with should statements focuses on the behavior of an object. -This helps you to think about the role of the object under test. - -Using a behavior-driven approach prevents the danger in assuming a one-to-one mapping of method names to -test method names. -As always, you get the most value by writing the tests first. - -For a more complete BDD framework, try RSpec http://rspec.rubyforge.org/ - -= Rake tasks - -You can define a Behaviors::ReportTask in your Rakefile to generate rake tasks that -summarize the behavior of your project. - -These tasks are named behaviors and behaviors_html. They will output to the -console or an html file in the doc directory with a list all of your should tests. - Behaviors::ReportTask.new do |t| - t.pattern = 'test/**/*_test.rb' - end - -You may also initialize the ReportTask with a custom name to associate with a particular suite of tests. - Behaviors::ReportTask.new(:widget_subsystem) do |t| - t.pattern = 'test/widgets/*_test.rb' - end - -The html report will be placed in the doc directory by default. -You can override this default by setting the html_dir in the ReportTask. - Behaviors::ReportTask.new do |t| - t.pattern = 'test/**/*_test.rb' - t.html_dir = 'behaviors_html_reports' - end -=end -module Behaviors - def should(behave,&block) - mname = "test_should_#{behave}" - if block - define_method mname, &block - else - puts ">>> UNIMPLEMENTED CASE: #{name.sub(/Test$/,'')} should #{behave}" - end - end -end diff --git a/vendor/behaviors/lib/behaviors/reporttask.rb b/vendor/behaviors/lib/behaviors/reporttask.rb deleted file mode 100644 index 4a5c9a4a..00000000 --- a/vendor/behaviors/lib/behaviors/reporttask.rb +++ /dev/null @@ -1,166 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'rake' -require 'rake/tasklib' - -module Behaviors -include Rake - - class ReportTask < TaskLib - attr_accessor :pattern - attr_accessor :html_dir - - def initialize(name=:behaviors) - @name = name - @html_dir = 'doc' - yield self if block_given? - define - end - - def define - desc "List behavioral definitions for the classes specified (use for= to further limit files included in report)" - task @name do - specifications.each do |spec| - puts "#{spec.name} should:\n" - spec.requirements.each do |req| - puts " - #{req}" - end - end - end - - desc "Generate html report of behavioral definitions for the classes specified (use for= to further limit files included in report)" - task "#{@name}_html" do - require 'erb' - txt =<<-EOS - - - - - -
Specifications
-<% specifications.each do |spec| %> -
-<%= spec.name %> should: -
    -<% spec.requirements.each do |req| %> -
  • <%= req %>
  • -<% end %> -
-
-<% end %> - - - EOS - output_dir = File.expand_path(@html_dir) - mkdir_p output_dir - output_filename = output_dir + "/behaviors.html" - File.open(output_filename,"w") do |f| - f.write ERB.new(txt).result(binding) - end - puts "(Wrote #{output_filename})" - end - end - - private - def test_files - test_list = FileList[@pattern] - if ENV['for'] - test_list = test_list.grep(/#{ENV['for']}/i) - end - test_list - end - - def specifications - test_files.map do |file| - spec = OpenStruct.new - m = %r".*/([^/].*)_test.rb".match(file) - class_name = titleize(m[1]) if m[1] - spec.name = class_name - spec.requirements = [] - File::readlines(file).each do |line| - if line =~ /^\s*should\s+\(?\s*["'](.*)["']/ - spec.requirements << $1 - end - end - spec - end - end - - ############################################################ - # STOLEN FROM inflector.rb - ############################################################ - #-- - # Copyright (c) 2005 David Heinemeier Hansson - # - # Permission is hereby granted, free of charge, to any person obtaining - # a copy of this software and associated documentation files (the - # "Software"), to deal in the Software without restriction, including - # without limitation the rights to use, copy, modify, merge, publish, - # distribute, sublicense, and/or sell copies of the Software, and to - # permit persons to whom the Software is furnished to do so, subject to - # the following conditions: - # - # The above copyright notice and this permission notice shall be - # included in all copies or substantial portions of the Software. - # - # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - #++ - def titleize(word) - humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize } - end - - def underscore(camel_cased_word) camel_cased_word.to_s.gsub(/::/, '/'). - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase - end - - def humanize(lower_case_and_underscored_word) - lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - end -end diff --git a/vendor/behaviors/test/behaviors_tasks_test.rb b/vendor/behaviors/test/behaviors_tasks_test.rb deleted file mode 100644 index 76943233..00000000 --- a/vendor/behaviors/test/behaviors_tasks_test.rb +++ /dev/null @@ -1,81 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'test/unit' -require 'fileutils' - -class BehaviorsTasksTest < Test::Unit::TestCase - include FileUtils - - def setup - @here = File.expand_path(File.dirname(__FILE__)) - @base_cmd = RUBY_PLATFORM[/mswin/] ? 'rake.cmd ' : 'rake ' - end - - # - # HELPERS - # - def run_behaviors_task - run_cmd "behaviors" - end - - def run_behaviors_html_task - run_cmd "behaviors_html" - end - - def run_cmd(cmd) - cd "#{@here}/tasks_test" do - @report = %x[ #{@base_cmd} #{cmd} ] - end - end - - def see_html_task_output_message - @html_output_filename = "#{@here}/tasks_test/behaviors_doc/behaviors.html" - assert_match(/Wrote #{@html_output_filename}/, @report) - end - - def see_that_html_report_file_exits - assert File.exists?(@html_output_filename), "html output file should exist" - end - - def html_report_file_should_contain(user_behaviors) - file_contents = File.read(@html_output_filename) - user_behaviors.each do |line| - assert_match(/#{line}/, file_contents) - end - rm_rf File.dirname(@html_output_filename) - end - - # - # TESTS - # - def test_that_behaviors_tasks_should_list_behavioral_definitions_for_the_classes_under_test - run_behaviors_task - user_behaviors = [ - "User should:", - " - be able set user name and age during construction", - " - be able to get user name and age", - " - be able to ask if a user is an adult" - ] - assert_match(/#{user_behaviors.join("\n")}/, @report) - end - - def test_that_behaviors_tasks_should_list_behavioral_definitions_for_the_classes_under_test_in_html_output - run_behaviors_html_task - see_html_task_output_message - see_that_html_report_file_exits - user_behaviors = [ - "User should:", - "be able set user name and age during construction", - "be able to get user name and age", - "be able to ask if a user is an adult" - ] - html_report_file_should_contain user_behaviors - end - -end diff --git a/vendor/behaviors/test/behaviors_test.rb b/vendor/behaviors/test/behaviors_test.rb deleted file mode 100644 index 5cea0145..00000000 --- a/vendor/behaviors/test/behaviors_test.rb +++ /dev/null @@ -1,58 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'test/unit' -require File.expand_path(File.dirname(__FILE__)) + '/../lib/behaviors' -require 'stringio' - -loading_developer_test_class_stdout = StringIO.new -saved_stdout = $stdout.dup -$stdout = loading_developer_test_class_stdout - -class DeveloperTest - extend Behaviors - attr_accessor :flunk_msg, :tested_code - - should "test their code" do - @tested_code = true - end - should "go to meetings" -end - -$stdout = saved_stdout -loading_developer_test_class_stdout.rewind -$loading_developer_test_class_output = loading_developer_test_class_stdout.read - -class BehaviorsTest < Test::Unit::TestCase - - - def setup - @target = DeveloperTest.new - assert_nil @target.tested_code, "block called too early" - end - - # - # TESTS - # - def test_should_called_with_a_block_defines_a_test - assert @target.methods.include?("test_should_test their code"), "Missing test method" - - @target.send("test_should_test their code") - - assert @target.tested_code, "block not called" - end - - def test_should_called_without_a_block_does_not_create_a_test_method - assert !@target.methods.include?("test_should_go to meetings"), "Should not have method" - end - - def test_should_called_without_a_block_will_give_unimplemented_output_when_class_loads - unimplemented_output = "UNIMPLEMENTED CASE: Developer should go to meetings" - assert_match(/#{unimplemented_output}/, $loading_developer_test_class_output) - end -end diff --git a/vendor/behaviors/test/tasks_test/Rakefile b/vendor/behaviors/test/tasks_test/Rakefile deleted file mode 100644 index ba71f715..00000000 --- a/vendor/behaviors/test/tasks_test/Rakefile +++ /dev/null @@ -1,19 +0,0 @@ -require 'rake' -require 'rake/testtask' - -here = File.expand_path(File.dirname(__FILE__)) -require "#{here}/../../lib/behaviors/reporttask" - -desc 'Default: run unit tests.' -task :default => :test - -Rake::TestTask.new(:test) do |t| - t.libs << "#{here}/../../lib" - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -Behaviors::ReportTask.new(:behaviors) do |t| - t.pattern = 'test/**/*_test.rb' - t.html_dir = 'behaviors_doc' -end diff --git a/vendor/behaviors/test/tasks_test/lib/user.rb b/vendor/behaviors/test/tasks_test/lib/user.rb deleted file mode 100644 index 7c8ead2e..00000000 --- a/vendor/behaviors/test/tasks_test/lib/user.rb +++ /dev/null @@ -1,10 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -class User -end diff --git a/vendor/behaviors/test/tasks_test/test/user_test.rb b/vendor/behaviors/test/tasks_test/test/user_test.rb deleted file mode 100644 index 1091cc94..00000000 --- a/vendor/behaviors/test/tasks_test/test/user_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'test/unit' -require 'behaviors' - -require 'user' - -class UserTest < Test::Unit::TestCase - extend Behaviors - - def setup - end - - should "be able set user name and age during construction" - should "be able to get user name and age" - should "be able to ask if a user is an adult" - def test_DELETEME - end -end diff --git a/vendor/hardmock/CHANGES b/vendor/hardmock/CHANGES deleted file mode 100644 index 4b5184ce..00000000 --- a/vendor/hardmock/CHANGES +++ /dev/null @@ -1,78 +0,0 @@ -Hardmock 1.3.7 - -* BUG FIX: expects! could not setup expectations for more than one concrete method on an object, since the method aliasing and rewriting was only taking place when the background mock instance was first created. This logic has been updated and now you can do all the things you'd expect. - -Hardmock 1.3.6 - -* BUG FIX: In Rails apps (and others) Hardmock and Fixtures battled viciously over "setup" and "teardown" and "method_added" (and any other clever test enhancement tool, namely Mocha) causing unpredictable results, notably failure to auto-verify mocks after teardown (leading to false positive tests). - * The newly-added TestUnitBeforeAfter provides TestCase.before_setup and TestCase.after_teardown -- formal test wrapping hooks -- lets Hardmock provide its preparation and auto-verify behavior without contending for setup/teardown supremacy. - -Hardmock 1.3.5 - -* Aliased should_receive => expects and and_return => returns for easier transition from rspec mock and flexmock users. - -Hardmock 1.3.4 - -* Prevents accidental stubbing and mocking on NilClasses - -Hardmock 1.3.3 - -* stubs! and expects! no longer require that their target methods exist in reality (this used to prevent you from stubbing methods that "exist" by virtue of "method_missing" -* Tweaked inner metaclass code to avoid collisions with rspec's "metaid" stuff -* Moved this project's Rake tasks into rake_tasks... otherwise Rails will load them, if Hardmock is installed as a Rails plugin -* Alias added: 'verify_hardmocks' is now an alias for 'verify_mocks' (some internal projects were using this modified method name as a means of cooexisting with mocha) - -Hardmock 1.3.2 - -November 2007 - -* adds 'with' as an alternate syntax for specifying argument expectations. - -Hardmock 1.3.1 - -October 2007 - -* Can use stubs! on a mock object -* expects! now generates mocked methods that can safely transfer runtime blocks to the mock instance itself -* No longer need to call "prepare_hardmock_control" when using stubs in the absence of mocks -* Stubs of concrete class or instance methods are restored to original state in teardown - -Hardmock 1.3.0 - -October 2007 - -* Adds stubs! and expects! method to all objects and classes to support concrete stubbing/mocking. - -Hardmock 1.2.3 - -Sat Apr 28 01:16:15 EDT 2007 - -* Re-release of 1.2.2 (which was canceled)... tasks moved to lib/tasks - -Hardmock 1.2.2 - -Sat Apr 28 00:41:30 EDT 2007 - -* assert_error has been broken out into its own lib file -* Gem package can now run all tests successfully -* Internal code refactoring; a number of classes that were defined in hardmock.rb are now in their own files - -Hardmock 1.2.1 - -Sat Apr 28 00:41:30 EDT 2007 - -* (botched release, see 1.2.2) - -Hardmock 1.2.0 - -* You can now use "expect" in place of "expects" if you must. -* "inspect" has been added to the list of methods NOT erased by MethodCleanout. - -Hardmock 1.1.0 - -* "expects" replaces "expect" ("expect" now raises Hardmock::DeprecationError) -* "verify_mocks" is now implicit in teardown, you needn't call it anymore -* Mocking methods that Mock would otherwise inherit from Object (eg, to_s) is now possible -* require 'hardmock' is all that's required to use the library now; no need to include in TestCase - -(previously called CMock, translated to Hardmock on 2006-12-10) diff --git a/vendor/hardmock/LICENSE b/vendor/hardmock/LICENSE deleted file mode 100644 index 396211e4..00000000 --- a/vendor/hardmock/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2006,2007 David Crosby at Atomic Object, LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/hardmock/README b/vendor/hardmock/README deleted file mode 100644 index 4650a2a3..00000000 --- a/vendor/hardmock/README +++ /dev/null @@ -1,70 +0,0 @@ -== Hardmock - -Strict, ordered mock objects using very lightweight syntax in your tests. - -== How - -The basic procedure for using Hardmock in your tests is: - -* require 'hardmock' (this happens automatically when being used as a Rails plugin) -* Create some mocks -* Setup some expectations -* Execute the target code -* Verification of calls is automatic in =teardown= - -The expectations you set when using mocks are strict and ordered. -Expectations you declare by creating and using mocks are all considered together. - -* Hardmock::Mock#expects will show you more examples -* Hardmock::SimpleExpectation will teach you more about expectation methods - -== Example - - create_mocks :garage, :car - - # Set some expectations - @garage.expects.open_door - @car.expects.start(:choke) - @car.expects.drive(:reverse, 5.mph) - - # Execute the code (this code is usually, obviously, in your class under test) - @garage.open_door - @car.start :choke - @car.drive :reverse, 5.mph - - verify_mocks # OPTIONAL, teardown will do this for you - -Expects @garage.open_door, @car.start(:choke) and @car.drive(:reverse, 5.mph) to be called in that order, with those specific arguments. -* Violations of expectations, such as mis-ordered calls, calls on wrong objects, or incorrect methods result in Hardmock::ExpectationError -* verify_mocks will raise VerifyError if not all expectations have been met. - -== Download and Install - -* Homepage: http://hardmock.rubyforge.org -* GEM or TGZ or ZIP: http://rubyforge.org/frs/?group_id=2742 -* Rails plugin: script/plugin install -* SVN access: svn co svn://rubyforge.org/var/svn/hardmock/trunk -* Developer SVN access: svn co svn://developername@rubyforge.org/var/svn/hardmock/trunk - -== Setup for Test::Unit - - require 'hardmock' - require 'assert_error' # OPTIONAL: this adds the TestUnit extension 'assert_error' - -NOTE: If installed as a Rails plugin, init.rb does this for you... nothing else is needed. - -== Setup for RSpec - -Get this into your spec helper or environment or Rakefile or wherever you prefer: - - Spec::Runner.configure do |configuration| - configuration.include Hardmock - configuration.after(:each) {verify_mocks} - end - -This puts the implicit conveniences into your spec context, like "create_mocks" etc, and also provides for automatic -"verify_mocks" after each Example is run. - -== Author -* David Crosby crosby at http://atomicobject.com -* (c) 2006,2007 Atomic Object LLC diff --git a/vendor/hardmock/Rakefile b/vendor/hardmock/Rakefile deleted file mode 100644 index aff126c2..00000000 --- a/vendor/hardmock/Rakefile +++ /dev/null @@ -1,8 +0,0 @@ -require 'rake' -require 'rubygems' - -HARDMOCK_VERSION = "1.3.7" - -Dir["rake_tasks/*.rake"].each { |f| load f } - -task :default => [ 'test:all' ] diff --git a/vendor/hardmock/config/environment.rb b/vendor/hardmock/config/environment.rb deleted file mode 100644 index ffa13344..00000000 --- a/vendor/hardmock/config/environment.rb +++ /dev/null @@ -1,20 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -# The path to the root directory of your application. -APP_ROOT = File.join(File.dirname(__FILE__), '..') - -ADDITIONAL_LOAD_PATHS = [] -ADDITIONAL_LOAD_PATHS.concat %w( - lib -).map { |dir| "#{APP_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } - -# Prepend to $LOAD_PATH -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } - -# Require any additional libraries needed diff --git a/vendor/hardmock/lib/assert_error.rb b/vendor/hardmock/lib/assert_error.rb deleted file mode 100644 index 7d32b725..00000000 --- a/vendor/hardmock/lib/assert_error.rb +++ /dev/null @@ -1,31 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'test/unit/assertions' - -module Test::Unit #:nodoc:# - module Assertions #:nodoc:# - # A better 'assert_raise'. +patterns+ can be one or more Regexps, or a literal String that - # must match the entire error message. - def assert_error(err_type,*patterns,&block) - assert_not_nil block, "assert_error requires a block" - assert((err_type and err_type.kind_of?(Class)), "First argument to assert_error has to be an error type") - err = assert_raise(err_type) do - block.call - end - patterns.each do |pattern| - case pattern - when Regexp - assert_match(pattern, err.message) - else - assert_equal pattern, err.message - end - end - end - end -end diff --git a/vendor/hardmock/lib/extend_test_unit.rb b/vendor/hardmock/lib/extend_test_unit.rb deleted file mode 100644 index 90d30513..00000000 --- a/vendor/hardmock/lib/extend_test_unit.rb +++ /dev/null @@ -1,22 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - - -require 'test/unit/testcase' -class Test::Unit::TestCase - include Hardmock -end - -require 'test_unit_before_after' -Test::Unit::TestCase.before_setup do |test| - test.prepare_hardmock_control -end - -Test::Unit::TestCase.after_teardown do |test| - test.verify_mocks -end diff --git a/vendor/hardmock/lib/hardmock.rb b/vendor/hardmock/lib/hardmock.rb deleted file mode 100644 index bc61428e..00000000 --- a/vendor/hardmock/lib/hardmock.rb +++ /dev/null @@ -1,94 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'hardmock/method_cleanout' -require 'hardmock/mock' -require 'hardmock/mock_control' -require 'hardmock/utils' -require 'hardmock/errors' -require 'hardmock/trapper' -require 'hardmock/expector' -require 'hardmock/expectation' -require 'hardmock/expectation_builder' -require 'hardmock/stubbing' - -module Hardmock - - # Create one or more new Mock instances in your test suite. - # Once created, the Mocks are accessible as instance variables in your test. - # Newly built Mocks are added to the full set of Mocks for this test, which will - # be verified when you call verify_mocks. - # - # create_mocks :donkey, :cat # Your test now has @donkey and @cat - # create_mock :dog # Test now has @donkey, @cat and @dog - # - # The first call returned a hash { :donkey => @donkey, :cat => @cat } - # and the second call returned { :dog => @dog } - # - # For more info on how to use your mocks, see Mock and Expectation - # - def create_mocks(*mock_names) - prepare_hardmock_control unless @main_mock_control - - mocks = {} - mock_names.each do |mock_name| - raise ArgumentError, "'nil' is not a valid name for a mock" if mock_name.nil? - mock_name = mock_name.to_s - mock_object = Mock.new(mock_name, @main_mock_control) - mocks[mock_name.to_sym] = mock_object - self.instance_variable_set "@#{mock_name}", mock_object - end - @all_mocks ||= {} - @all_mocks.merge! mocks - - return mocks.clone - end - - def prepare_hardmock_control - if @main_mock_control.nil? - @main_mock_control = MockControl.new - $main_mock_control = @main_mock_control - else - raise "@main_mock_control is already setup for this test!" - end - end - - alias :create_mock :create_mocks - - # Ensures that all expectations have been met. If not, VerifyException is - # raised. - # - # You normally won't need to call this yourself. Within Test::Unit::TestCase, this will be done automatically at teardown time. - # - # * +force+ -- if +false+, and a VerifyError or ExpectationError has already occurred, this method will not raise. This is to help you suppress repeated errors when if you're calling #verify_mocks in the teardown method of your test suite. BE WARNED - only use this if you're sure you aren't obscuring useful information. Eg, if your code handles exceptions internally, and an ExpectationError gets gobbled up by your +rescue+ block, the cause of failure for your test may be hidden from you. For this reason, #verify_mocks defaults to force=true as of Hardmock 1.0.1 - def verify_mocks(force=true) - return unless @main_mock_control - return if @main_mock_control.disappointed? and !force - @main_mock_control.verify - ensure - @main_mock_control.clear_expectations if @main_mock_control - $main_mock_control = nil - reset_stubs - end - - alias :verify_hardmocks :verify_mocks - - # Purge the main MockControl of all expectations, restore all concrete stubbed/mocked methods - def clear_expectations - @main_mock_control.clear_expectations if @main_mock_control - reset_stubs - $main_mock_control = nil - end - - def reset_stubs - Hardmock.restore_all_replaced_methods - end - -end - -require 'extend_test_unit' diff --git a/vendor/hardmock/lib/hardmock/errors.rb b/vendor/hardmock/lib/hardmock/errors.rb deleted file mode 100644 index 63131678..00000000 --- a/vendor/hardmock/lib/hardmock/errors.rb +++ /dev/null @@ -1,30 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -module Hardmock - # Raised when: - # * Unexpected method is called on a mock object - # * Bad arguments passed to an expected call - class ExpectationError < StandardError #:nodoc:# - end - - # Raised for methods that should no longer be called. Hopefully, the exception message contains helpful alternatives. - class DeprecationError < StandardError #:nodoc:# - end - - # Raised when stubbing fails - class StubbingError < StandardError #:nodoc:# - end - - # Raised when it is discovered that an expected method call was never made. - class VerifyError < StandardError #:nodoc:# - def initialize(msg,unmet_expectations) - super("#{msg}:" + unmet_expectations.map { |ex| "\n * #{ex.to_s}" }.join) - end - end -end diff --git a/vendor/hardmock/lib/hardmock/expectation.rb b/vendor/hardmock/lib/hardmock/expectation.rb deleted file mode 100644 index bc0a8aa4..00000000 --- a/vendor/hardmock/lib/hardmock/expectation.rb +++ /dev/null @@ -1,237 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'hardmock/utils' - -module Hardmock - class Expectation - include Utils - attr_reader :block_value - - def initialize(options) #:nodoc: - @options = options - end - - def apply_method_call(mock,mname,args,block) #:nodoc: - unless @options[:mock].equal?(mock) - raise anger("Wrong object", mock,mname,args) - end - unless @options[:method] == mname - raise anger("Wrong method",mock,mname,args) - end - - # Tester-defined block to invoke at method-call-time: - expectation_block = @options[:block] - - expected_args = @options[:arguments] - # if we have a block, we can skip the argument check if none were specified - unless (expected_args.nil? || expected_args.empty?) && expectation_block && !@options[:suppress_arguments_to_block] - unless expected_args == args - raise anger("Wrong arguments",mock,mname,args) - end - end - - relayed_args = args.dup - if block - if expectation_block.nil? - # Can't handle a runtime block without an expectation block - raise ExpectationError.new("Unexpected block provided to #{to_s}") - else - # Runtime blocks are passed as final argument to the expectation block - unless @options[:suppress_arguments_to_block] - relayed_args << block - else - # Arguments suppressed; send only the block - relayed_args = [block] - end - end - end - - # Run the expectation block: - @block_value = expectation_block.call(*relayed_args) if expectation_block - - raise @options[:raises] unless @options[:raises].nil? - - return_value = @options[:returns] - if return_value.nil? - return @block_value - else - return return_value - end - end - - # Set the return value for an expected method call. - # Eg, - # @cash_machine.expects.withdraw(20,:dollars).returns(20.00) - def returns(val) - @options[:returns] = val - self - end - alias_method :and_return, :returns - - # Set the arguments for an expected method call. - # Eg, - # @cash_machine.expects.deposit.with(20, "dollars").returns(:balance => "20") - def with(*args) - @options[:arguments] = args - self - end - - # Rig an expected method to raise an exception when the mock is invoked. - # - # Eg, - # @cash_machine.expects.withdraw(20,:dollars).raises "Insufficient funds" - # - # The argument can be: - # * an Exception -- will be used directly - # * a String -- will be used as the message for a RuntimeError - # * nothing -- RuntimeError.new("An Error") will be raised - def raises(err=nil) - case err - when Exception - @options[:raises] = err - when String - @options[:raises] = RuntimeError.new(err) - else - @options[:raises] = RuntimeError.new("An Error") - end - self - end - - # Convenience method: assumes +block_value+ is set, and is set to a Proc - # (or anything that responds to 'call') - # - # light_event = @traffic_light.trap.subscribe(:light_changes) - # - # # This code will meet the expectation: - # @traffic_light.subscribe :light_changes do |color| - # puts color - # end - # - # The color-handling block is now stored in light_event.block_value - # - # The block can be invoked like this: - # - # light_event.trigger :red - # - # See Mock#trap and Mock#expects for information on using expectation objects - # after they are set. - # - def trigger(*block_arguments) - unless block_value - raise ExpectationError.new("No block value is currently set for expectation #{to_s}") - end - unless block_value.respond_to?(:call) - raise ExpectationError.new("Can't apply trigger to #{block_value} for expectation #{to_s}") - end - block_value.call *block_arguments - end - - # Used when an expected method accepts a block at runtime. - # When the expected method is invoked, the block passed to - # that method will be invoked as well. - # - # NOTE: ExpectationError will be thrown upon running the expected method - # if the arguments you set up in +yields+ do not properly match up with - # the actual block that ends up getting passed. - # - # == Examples - # Single invocation: The block passed to +lock_down+ gets invoked - # once with no arguments: - # - # @safe_zone.expects.lock_down.yields - # - # # (works on code that looks like:) - # @safe_zone.lock_down do - # # ... this block invoked once - # end - # - # Multi-parameter blocks: The block passed to +each_item+ gets - # invoked twice, with :item1 the first time, and with - # :item2 the second time: - # - # @fruit_basket.expects.each_with_index.yields [:apple,1], [:orange,2] - # - # # (works on code that looks like:) - # @fruit_basket.each_with_index do |fruit,index| - # # ... this block invoked with fruit=:apple, index=1, - # # ... and then with fruit=:orange, index=2 - # end - # - # Arrays can be passed as arguments too... if the block - # takes a single argument and you want to pass a series of arrays into it, - # that will work as well: - # - # @list_provider.expects.each_list.yields [1,2,3], [4,5,6] - # - # # (works on code that looks like:) - # @list_provider.each_list do |list| - # # ... list is [1,2,3] the first time - # # ... list is [4,5,6] the second time - # end - # - # Return value: You can set the return value for the method that - # accepts the block like so: - # - # @cruncher.expects.do_things.yields(:bean1,:bean2).returns("The Results") - # - # Raising errors: You can set the raised exception for the method that - # accepts the block. NOTE: the error will be raised _after_ the block has - # been invoked. - # - # # :bean1 and :bean2 will be passed to the block, then an error is raised: - # @cruncher.expects.do_things.yields(:bean1,:bean2).raises("Too crunchy") - # - def yields(*items) - @options[:suppress_arguments_to_block] = true - if items.empty? - # Yield once - @options[:block] = lambda do |block| - if block.arity != 0 and block.arity != -1 - raise ExpectationError.new("The given block was expected to have no parameter count; instead, got #{block.arity} to <#{to_s}>") - end - block.call - end - else - # Yield one or more specific items - @options[:block] = lambda do |block| - items.each do |item| - if item.kind_of?(Array) - if block.arity == item.size - # Unfold the array into the block's arguments: - block.call *item - elsif block.arity == 1 - # Just pass the array in - block.call item - else - # Size mismatch - raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>") - end - else - if block.arity != 1 - # Size mismatch - raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>") - end - block.call item - end - end - end - end - self - end - - def to_s # :nodoc: - format_method_call_string(@options[:mock],@options[:method],@options[:arguments]) - end - - private - def anger(msg, mock,mname,args) - ExpectationError.new("#{msg}: expected call <#{to_s}> but was <#{format_method_call_string(mock,mname,args)}>") - end - end -end diff --git a/vendor/hardmock/lib/hardmock/expectation_builder.rb b/vendor/hardmock/lib/hardmock/expectation_builder.rb deleted file mode 100644 index d6b15575..00000000 --- a/vendor/hardmock/lib/hardmock/expectation_builder.rb +++ /dev/null @@ -1,17 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'hardmock/expectation' - -module Hardmock - class ExpectationBuilder #:nodoc: - def build_expectation(options) - Expectation.new(options) - end - end -end diff --git a/vendor/hardmock/lib/hardmock/expector.rb b/vendor/hardmock/lib/hardmock/expector.rb deleted file mode 100644 index 98d41669..00000000 --- a/vendor/hardmock/lib/hardmock/expector.rb +++ /dev/null @@ -1,34 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'hardmock/method_cleanout' -require 'hardmock/errors' - -module Hardmock - class Expector #:nodoc: - include MethodCleanout - - def initialize(mock,mock_control,expectation_builder) - @mock = mock - @mock_control = mock_control - @expectation_builder = expectation_builder - end - - def method_missing(mname, *args, &block) - expectation = @expectation_builder.build_expectation( - :mock => @mock, - :method => mname, - :arguments => args, - :block => block) - - @mock_control.add_expectation expectation - expectation - end - end - -end diff --git a/vendor/hardmock/lib/hardmock/method_cleanout.rb b/vendor/hardmock/lib/hardmock/method_cleanout.rb deleted file mode 100644 index 2dedf374..00000000 --- a/vendor/hardmock/lib/hardmock/method_cleanout.rb +++ /dev/null @@ -1,41 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - - -module Hardmock #:nodoc: - module MethodCleanout #:nodoc: - SACRED_METHODS = %w{ - __id__ - __send__ - equal? - object_id - send - nil? - class - kind_of? - respond_to? - inspect - method - to_s - instance_variables - instance_eval - == - hm_metaclass - hm_meta_eval - hm_meta_def - } - - def self.included(base) #:nodoc: - base.class_eval do - instance_methods.each do |m| - undef_method m unless SACRED_METHODS.include?(m.to_s) - end - end - end - end -end diff --git a/vendor/hardmock/lib/hardmock/mock.rb b/vendor/hardmock/lib/hardmock/mock.rb deleted file mode 100644 index e56cf9ea..00000000 --- a/vendor/hardmock/lib/hardmock/mock.rb +++ /dev/null @@ -1,188 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - - -module Hardmock - # Mock is used to set expectations in your test. Most of the time you'll use - # #expects to create expectations. - # - # Aside from the scant few control methods (like +expects+, +trap+ and +_verify+) - # all calls made on a Mock instance will be immediately applied to the internal - # expectation mechanism. - # - # * If the method call was expected and all the parameters match properly, execution continues - # * If the expectation was configured with an expectation block, the block is invoked - # * If the expectation was set up to raise an error, the error is raised now - # * If the expectation was set up to return a value, it is returned - # * If the method call was _not_ expected, or the parameter values are wrong, an ExpectationError is raised. - class Mock - include Hardmock::MethodCleanout - - # Create a new Mock instance with a name and a MockControl to support it. - # If not given, a MockControl is made implicitly for this Mock alone; this means - # expectations for this mock are not tied to other expectations in your test. - # - # It's not recommended to use a Mock directly; see Hardmock and - # Hardmock#create_mocks for the more wholistic approach. - def initialize(name, mock_control=nil) - @name = name - @control = mock_control || MockControl.new - @expectation_builder = ExpectationBuilder.new - end - - def inspect - "" - end - - # Begin declaring an expectation for this Mock. - # - # == Simple Examples - # Expect the +customer+ to be queried for +account+, and return "The - # Account": - # @customer.expects.account.returns "The Account" - # - # Expect the +withdraw+ method to be called, and raise an exception when it - # is (see Expectation#raises for more info): - # @cash_machine.expects.withdraw(20,:dollars).raises("not enough money") - # - # Expect +customer+ to have its +user_name+ set - # @customer.expects.user_name = 'Big Boss' - # - # Expect +customer+ to have its +user_name+ set, and raise a RuntimeException when - # that happens: - # @customer.expects('user_name=', "Big Boss").raises "lost connection" - # - # Expect +evaluate+ to be passed a block, and when that happens, pass a value - # to the block (see Expectation#yields for more info): - # @cruncher.expects.evaluate.yields("some data").returns("some results") - # - # - # == Expectation Blocks - # To do special handling of expected method calls when they occur, you - # may pass a block to your expectation, like: - # @page_scraper.expects.handle_content do |address,request,status| - # assert_not_nil address, "Can't abide nil addresses" - # assert_equal "http-get", request.method, "Can only handle GET" - # assert status > 200 and status < 300, status, "Failed status" - # "Simulated results #{request.content.downcase}" - # end - # In this example, when page_scraper.handle_content is called, its - # three arguments are passed to the expectation block and evaluated - # using the above assertions. The last value in the block will be used - # as the return value for +handle_content+ - # - # You may specify arguments to the expected method call, just like any normal - # expectation, and those arguments will be pre-validated before being passed - # to the expectation block. This is useful when you know all of the - # expected values but still need to do something programmatic. - # - # If the method being invoked on the mock accepts a block, that block will be - # passed to your expectation block as the last (or only) argument. Eg, the - # convenience method +yields+ can be replaced with the more explicit: - # @cruncher.expects.evaluate do |block| - # block.call "some data" - # "some results" - # end - # - # The result value of the expectation block becomes the return value for the - # expected method call. This can be overidden by using the +returns+ method: - # @cruncher.expects.evaluate do |block| - # block.call "some data" - # "some results" - # end.returns("the actual value") - # - # Additionally, the resulting value of the expectation block is stored - # in the +block_value+ field on the expectation. If you've saved a reference - # to your expectation, you may retrieve the block value once the expectation - # has been met. - # - # evaluation_event = @cruncher.expects.evaluate do |block| - # block.call "some data" - # "some results" - # end.returns("the actual value") - # - # result = @cruncher.evaluate do |input| - # puts input # => 'some data' - # end - # # result is 'the actual value' - # - # evaluation_event.block_value # => 'some results' - # - def expects(*args, &block) - expector = Expector.new(self,@control,@expectation_builder) - # If there are no args, we return the Expector - return expector if args.empty? - # If there ARE args, we set up the expectation right here and return it - expector.send(args.shift.to_sym, *args, &block) - end - alias_method :expect, :expects - alias_method :should_receive, :expects - - # Special-case convenience: #trap sets up an expectation for a method - # that will take a block. That block, when sent to the expected method, will - # be trapped and stored in the expectation's +block_value+ field. - # The Expectation#trigger method may then be used to invoke that block. - # - # Like +expects+, the +trap+ mechanism can be followed by +raises+ or +returns+. - # - # _Unlike_ +expects+, you may not use an expectation block with +trap+. If - # the expected method takes arguments in addition to the block, they must - # be specified in the arguments to the +trap+ call itself. - # - # == Example - # - # create_mocks :address_book, :editor_form - # - # # Expect a subscription on the :person_added event for @address_book: - # person_event = @address_book.trap.subscribe(:person_added) - # - # # The runtime code would look like: - # @address_book.subscribe :person_added do |person_name| - # @editor_form.name = person_name - # end - # - # # At this point, the expectation for 'subscribe' is met and the - # # block has been captured. But we're not done: - # @editor_form.expects.name = "David" - # - # # Now invoke the block we trapped earlier: - # person_event.trigger "David" - # - # verify_mocks - def trap(*args) - Trapper.new(self,@control,ExpectationBuilder.new) - end - - def method_missing(mname,*args) #:nodoc: - block = nil - block = Proc.new if block_given? - @control.apply_method_call(self,mname,args,block) - end - - - def _control #:nodoc: - @control - end - - def _name #:nodoc: - @name - end - - # Verify that all expectations are fulfilled. NOTE: this method triggers - # validation on the _control_ for this mock, so all Mocks that share the - # MockControl with this instance will be included in the verification. - # - # Only use this method if you are managing your own Mocks and their controls. - # - # Normal usage of Hardmock doesn't require you to call this; let - # Hardmock#verify_mocks do it for you. - def _verify - @control.verify - end - end -end diff --git a/vendor/hardmock/lib/hardmock/mock_control.rb b/vendor/hardmock/lib/hardmock/mock_control.rb deleted file mode 100644 index 78df27b1..00000000 --- a/vendor/hardmock/lib/hardmock/mock_control.rb +++ /dev/null @@ -1,61 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'hardmock/utils' - -module Hardmock - class MockControl #:nodoc: - include Utils - attr_accessor :name - - def initialize - clear_expectations - end - - def happy? - @expectations.empty? - end - - def disappointed? - @disappointed - end - - def add_expectation(expectation) -# puts "MockControl #{self.object_id.to_s(16)} adding expectation: #{expectation}" - @expectations << expectation - end - - def apply_method_call(mock,mname,args,block) - # Are we even expecting any sort of call? - if happy? - @disappointed = true - raise ExpectationError.new("Surprise call to #{format_method_call_string(mock,mname,args)}") - end - - begin - @expectations.shift.apply_method_call(mock,mname,args,block) - rescue Exception => ouch - @disappointed = true - raise ouch - end - end - - def verify -# puts "MockControl #{self.object_id.to_s(16)} verify: happy? #{happy?}" - @disappointed = !happy? - raise VerifyError.new("Unmet expectations", @expectations) unless happy? - end - - def clear_expectations - @expectations = [] - @disappointed = false - end - - end - -end diff --git a/vendor/hardmock/lib/hardmock/stubbing.rb b/vendor/hardmock/lib/hardmock/stubbing.rb deleted file mode 100644 index 9c47cc4b..00000000 --- a/vendor/hardmock/lib/hardmock/stubbing.rb +++ /dev/null @@ -1,218 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - - - -# Stubbing support -# -# Stubs methods on classes and instances -# - -# Why's "metaid.rb" stuff crunched down: -class Object #:nodoc:# - def hm_metaclass #:nodoc:# - class << self - self - end - end - - def hm_meta_eval(&blk) #:nodoc:# - hm_metaclass.instance_eval(&blk) - end - - def hm_meta_def(name, &blk) #:nodoc:# - hm_meta_eval { define_method name, &blk } - end -end - - - -module Hardmock - - # == Hardmock: Stubbing and Mocking Concrete Methods - # - # Hardmock lets you stub and/or mock methods on concrete classes or objects. - # - # * To "stub" a concrete method is to rig it to return the same thing always, disregarding any arguments. - # * To "mock" a concrete method is to surplant its funcionality by delegating to a mock object who will cover this behavior. - # - # Mocked methods have their expectations considered along with all other mock object expectations. - # - # If you use stubbing or concrete mocking in the absence (or before creation) of other mocks, you need to invoke prepare_hardmock_control. - # Once verify_mocks or clear_expectaions is called, the overriden behavior in the target objects is restored. - # - # == Examples - # - # River.stubs!(:sounds_like).returns("gurgle") - # - # River.expects!(:jump).returns("splash") - # - # rogue.stubs!(:sounds_like).returns("pshshsh") - # - # rogue.expects!(:rawhide_tanning_solvents).returns("giant snapping turtles") - # - module Stubbing - # Exists only for documentation - end - - class ReplacedMethod #:nodoc:# - attr_reader :target, :method_name - - def initialize(target, method_name) - @target = target - @method_name = method_name - - Hardmock.track_replaced_method self - end - end - - class StubbedMethod < ReplacedMethod #:nodoc:# - def invoke(args) - raise @raises if @raises - @return_value - end - - def returns(stubbed_return) - @return_value = stubbed_return - end - - def raises(err) - err = RuntimeError.new(err) unless err.kind_of?(Exception) - @raises = err - end - end - - class ::Object - def stubs!(method_name) - method_name = method_name.to_s - already_stubbed = Hardmock.has_replaced_method?(self, method_name) - - stubbed_method = Hardmock::StubbedMethod.new(self, method_name) - - - unless _is_mock? or already_stubbed - if methods.include?(method_name.to_s) - hm_meta_eval do - alias_method "_hardmock_original_#{method_name}".to_sym, method_name.to_sym - end - end - end - - hm_meta_def method_name do |*args| - stubbed_method.invoke(args) - end - - stubbed_method - end - - def expects!(method_name, *args, &block) - if self._is_mock? - raise Hardmock::StubbingError, "Cannot use 'expects!(:#{method_name})' on a Mock object; try 'expects' instead" - end - - method_name = method_name.to_s - - @_my_mock = Mock.new(_my_name, $main_mock_control) if @_my_mock.nil? - - unless Hardmock.has_replaced_method?(self, method_name) - # Track the method as replaced - Hardmock::ReplacedMethod.new(self, method_name) - - # Preserver original implementation of the method by aliasing it away - if methods.include?(method_name) - hm_meta_eval do - alias_method "_hardmock_original_#{method_name}".to_sym, method_name.to_sym - end - end - - # Re-define the method to utilize our patron mock instance. - # (This global-temp-var thing is hokey but I was having difficulty generating - # code for the meta class.) - begin - $method_text_temp = %{ - def #{method_name}(*args,&block) - @_my_mock.__send__(:#{method_name}, *args, &block) - end - } - class << self - eval $method_text_temp - end - ensure - $method_text_temp = nil - end - end - - return @_my_mock.expects(method_name, *args, &block) - end - - def _is_mock? - self.kind_of?(Mock) - end - - def _my_name - self.kind_of?(Class) ? self.name : self.class.name - end - - def _clear_mock - @_my_mock = nil - end - - end - - class ::NilClass - # Use this only if you really mean it - alias_method :intentionally_stubs!, :stubs! - - # Use this only if you really mean it - alias_method :intentionally_expects!, :expects! - - # Overridden to protect against accidental nil reference self delusion - def stubs!(mname) - raise StubbingError, "Cannot stub #{mname} method on nil. (If you really mean to, try 'intentionally_stubs!')" - end - - # Overridden to protect against accidental nil reference self delusion - def expects!(mname, *args) - raise StubbingError, "Cannot mock #{mname} method on nil. (If you really mean to, try 'intentionally_expects!')" - end - end - - class << self - def track_replaced_method(replaced_method) - all_replaced_methods << replaced_method - end - - def all_replaced_methods - $all_replaced_methods ||= [] - end - - def has_replaced_method?(obj, method_name) - hits = all_replaced_methods.select do |replaced| - (replaced.target.object_id == obj.object_id) and (replaced.method_name.to_s == method_name.to_s) - end - return !hits.empty? - end - - def restore_all_replaced_methods - all_replaced_methods.each do |replaced| - unless replaced.target._is_mock? - backed_up = "_hardmock_original_#{replaced.method_name}" - if replaced.target.methods.include?(backed_up) - replaced.target.hm_meta_eval do - alias_method replaced.method_name.to_sym, backed_up.to_sym - end - end - replaced.target._clear_mock - end - end - all_replaced_methods.clear - end - end - -end - diff --git a/vendor/hardmock/lib/hardmock/trapper.rb b/vendor/hardmock/lib/hardmock/trapper.rb deleted file mode 100644 index 103bdf21..00000000 --- a/vendor/hardmock/lib/hardmock/trapper.rb +++ /dev/null @@ -1,39 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'test/unit/assertions' -require 'hardmock/errors' - -module Hardmock - class Trapper #:nodoc: - include Hardmock::MethodCleanout - - def initialize(mock,mock_control,expectation_builder) - @mock = mock - @mock_control = mock_control - @expectation_builder = expectation_builder - end - - def method_missing(mname, *args) - if block_given? - raise ExpectationError.new("Don't pass blocks when using 'trap' (setting exepectations for '#{mname}')") - end - - the_block = lambda { |target_block| target_block } - expectation = @expectation_builder.build_expectation( - :mock => @mock, - :method => mname, - :arguments => args, - :suppress_arguments_to_block => true, - :block => the_block) - - @mock_control.add_expectation expectation - expectation - end - end -end diff --git a/vendor/hardmock/lib/hardmock/utils.rb b/vendor/hardmock/lib/hardmock/utils.rb deleted file mode 100644 index 27e16652..00000000 --- a/vendor/hardmock/lib/hardmock/utils.rb +++ /dev/null @@ -1,17 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - - -module Hardmock - module Utils #:nodoc: - def format_method_call_string(mock,mname,args) - arg_string = args.map { |a| a.inspect }.join(', ') - call_text = "#{mock._name}.#{mname}(#{arg_string})" - end - end -end diff --git a/vendor/hardmock/lib/test_unit_before_after.rb b/vendor/hardmock/lib/test_unit_before_after.rb deleted file mode 100644 index e2cb397e..00000000 --- a/vendor/hardmock/lib/test_unit_before_after.rb +++ /dev/null @@ -1,177 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'test/unit' -require 'test/unit/testcase' -require 'test/unit/assertions' - -module Test #:nodoc:# - module Unit #:nodoc:# - - # == TestCase Modifications - # - # Monkey-patch to provide a formal mechanism for appending actions to be executed after teardown. - # Use after_teardown to define one or more actions to be executed after teardown for ALL tests. - # - # COMING SOON? - # * (maybe?) Hooks for before_teardown, after_setup, on_error - # * (maybe?) Options for positional control, eg, after_teardown :before_other_actions - # * (maybe?) Provide tagging/filtering so action execution can be controlled specifically? - # - # == Usage - # - # Invoke TestCase.after_teardown with optional parameter, which will be invoked with a reference - # to the test instance that has just been torn down. - # - # Example: - # - # Test::Unit::TestCase.after_teardown do |test| - # test.verify_mocks - # end - # - # == Justification - # - # There are a number of tools and libraries that play fast-n-loose with setup and teardown by - # wrapping them, and by overriding method_added as a means of upholding special setup/teardown - # behavior, usually by re-wrapping newly defined user-level setup/teardown methods. - # mocha and active_record/fixtures (and previously, hardmock) will fight for this - # territory with often unpredictable results. - # - # We wouldn't have to battle if Test::Unit provided a formal pre- and post- hook mechanism. - # - class TestCase - - class << self - - # Define an action to be run after teardown. Subsequent calls result in - # multiple actions. The block will be given a reference to the test - # being executed. - # - # Example: - # - # Test::Unit::TestCase.after_teardown do |test| - # test.verify_mocks - # end - def after_teardown(&block) - post_teardown_actions << block - end - - # Used internally. Access the list of post teardown actions for to be - # used by all tests. - def post_teardown_actions - @@post_teardown_actions ||= [] - end - - # Define an action to be run before setup. Subsequent calls result in - # multiple actions, EACH BEING PREPENDED TO THE PREVIOUS. - # The block will be given a reference to the test being executed. - # - # Example: - # - # Test::Unit::TestCase.before_setup do |test| - # test.prepare_hardmock_control - # end - def before_setup(&block) - pre_setup_actions.unshift block - end - - # Used internally. Access the list of post teardown actions for to be - # used by all tests. - def pre_setup_actions - @@pre_setup_actions ||= [] - end - end - - # OVERRIDE: This is a reimplementation of the default "run", updated to - # execute actions after teardown. - def run(result) - yield(STARTED, name) - @_result = result - begin - execute_pre_setup_actions(self) - setup - __send__(@method_name) - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) - rescue Exception - raise if should_passthru_exception($!) # See implementation; this is for pre-1.8.6 compat - add_error($!) - ensure - begin - teardown - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) - rescue Exception - raise if should_passthru_exception($!) # See implementation; this is for pre-1.8.6 compat - add_error($!) - ensure - execute_post_teardown_actions(self) - end - end - result.add_run - yield(FINISHED, name) - end - - private - - # Run through the after_teardown actions, treating failures and errors - # in the same way that "run" does: they are reported, and the remaining - # actions are executed. - def execute_post_teardown_actions(test_instance) - self.class.post_teardown_actions.each do |action| - begin - action.call test_instance - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) - rescue Exception - raise if should_passthru_exception($!) - add_error($!) - end - end - end - - # Run through the before_setup actions. - # Failures or errors cause execution to stop. - def execute_pre_setup_actions(test_instance) - self.class.pre_setup_actions.each do |action| -# begin - action.call test_instance -# rescue Test::Unit::AssertionFailedError => e -# add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) -# rescue Exception -# raise if should_passthru_exception($!) -# add_error($!) -# end - end - end - - # Make sure that this extension doesn't show up in failure backtraces - def auxiliary_backtrace_filter(trace) - trace.reject { |x| x =~ /test_unit_before_after/ } - end - - # Is the given error of the type that we allow to fly out (rather than catching it)? - def should_passthru_exception(ex) - return passthrough_exception_types.include?($!.class) - end - - # Provide a list of exception types that are to be allowed to explode out. - # Pre-ruby-1.8.6 doesn't use this functionality, so the PASSTHROUGH_EXCEPTIONS - # constant won't be defined. This methods defends against that and returns - # an empty list instead. - def passthrough_exception_types - begin - return PASSTHROUGH_EXCEPTIONS - rescue NameError - # older versions of test/unit do not have PASSTHROUGH_EXCEPTIONS constant - return [] - end - end - end - end -end diff --git a/vendor/hardmock/rake_tasks/rdoc.rake b/vendor/hardmock/rake_tasks/rdoc.rake deleted file mode 100644 index c2427348..00000000 --- a/vendor/hardmock/rake_tasks/rdoc.rake +++ /dev/null @@ -1,27 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'rake/rdoctask' -require File.expand_path(File.dirname(__FILE__) + "/rdoc_options.rb") - -namespace :doc do - - desc "Generate RDoc documentation" - Rake::RDocTask.new { |rdoc| - rdoc.rdoc_dir = 'doc' - rdoc.title = "Hardmock: Strict expectation-based mock object library " - add_rdoc_options(rdoc.options) - rdoc.rdoc_files.include('lib/**/*.rb', 'README','CHANGES','LICENSE') - } - - task :show => [ 'doc:rerdoc' ] do - sh "open doc/index.html" - end - -end - diff --git a/vendor/hardmock/rake_tasks/rdoc_options.rb b/vendor/hardmock/rake_tasks/rdoc_options.rb deleted file mode 100644 index bc53b94d..00000000 --- a/vendor/hardmock/rake_tasks/rdoc_options.rb +++ /dev/null @@ -1,12 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - - -def add_rdoc_options(options) - options << '--line-numbers' << '--inline-source' << '--main' << 'README' << '--title' << 'Hardmock' -end diff --git a/vendor/hardmock/rake_tasks/test.rake b/vendor/hardmock/rake_tasks/test.rake deleted file mode 100644 index 203640a6..00000000 --- a/vendor/hardmock/rake_tasks/test.rake +++ /dev/null @@ -1,30 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require 'rake/testtask' - -namespace :test do - - desc "Run unit tests" - Rake::TestTask.new("units") { |t| - t.libs << "test" - t.pattern = 'test/unit/*_test.rb' - t.verbose = true - } - - desc "Run functional tests" - Rake::TestTask.new("functional") { |t| - t.libs << "test" - t.pattern = 'test/functional/*_test.rb' - t.verbose = true - } - - desc "Run all the tests" - task :all => [ 'test:units', 'test:functional' ] - -end diff --git a/vendor/hardmock/test/functional/assert_error_test.rb b/vendor/hardmock/test/functional/assert_error_test.rb deleted file mode 100644 index 6f56eaf4..00000000 --- a/vendor/hardmock/test/functional/assert_error_test.rb +++ /dev/null @@ -1,60 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'assert_error' - -class AssertErrorTest < Test::Unit::TestCase - - it "specfies an error type and message that should be raised" do - assert_error RuntimeError, "Too funky" do - raise RuntimeError.new("Too funky") - end - end - - it "flunks if the error message is wrong" do - err = assert_raise Test::Unit::AssertionFailedError do - assert_error RuntimeError, "not good" do - raise RuntimeError.new("Too funky") - end - end - assert_match(/not good/i, err.message) - assert_match(/too funky/i, err.message) - end - - it "flunks if the error type is wrong" do - err = assert_raise Test::Unit::AssertionFailedError do - assert_error StandardError, "Too funky" do - raise RuntimeError.new("Too funky") - end - end - assert_match(/StandardError/i, err.message) - assert_match(/RuntimeError/i, err.message) - end - - it "can match error message text using a series of Regexps" do - assert_error StandardError, /too/i, /funky/i do - raise StandardError.new("Too funky") - end - end - - it "flunks if the error message doesn't match all the Regexps" do - err = assert_raise Test::Unit::AssertionFailedError do - assert_error StandardError, /way/i, /too/i, /funky/i do - raise StandardError.new("Too funky") - end - end - assert_match(/way/i, err.message) - end - - it "can operate without any message specification" do - assert_error StandardError do - raise StandardError.new("ooof") - end - end -end diff --git a/vendor/hardmock/test/functional/auto_verify_test.rb b/vendor/hardmock/test/functional/auto_verify_test.rb deleted file mode 100644 index a92e6183..00000000 --- a/vendor/hardmock/test/functional/auto_verify_test.rb +++ /dev/null @@ -1,186 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'fileutils' - -class AutoVerifyTest < Test::Unit::TestCase - - def setup - @expect_unmet_expectations = true - end - - def teardown - remove_temp_test_file - end - - # - # TESTS - # - - it "auto-verifies all mocks in teardown" do - write_and_execute_test - end - - it "auto-verifies even if user defines own teardown" do - @teardown_code =<<-EOM - def teardown - # just in the way - end - EOM - write_and_execute_test - end - - should "not obscure normal failures when verification fails" do - @test_code =<<-EOM - def test_setup_doomed_expectation - create_mock :automobile - @automobile.expects.start - flunk "natural failure" - end - EOM - @expect_failures = 1 - write_and_execute_test - end - - should "not skip user-defined teardown when verification fails" do - @teardown_code =<<-EOM - def teardown - puts "User teardown" - end - EOM - write_and_execute_test - assert_output_contains(/User teardown/) - end - - it "is quiet when verification is ok" do - @test_code =<<-EOM - def test_ok - create_mock :automobile - @automobile.expects.start - @automobile.start - end - EOM - @teardown_code =<<-EOM - def teardown - puts "User teardown" - end - EOM - @expect_unmet_expectations = false - @expect_failures = 0 - @expect_errors = 0 - write_and_execute_test - assert_output_contains(/User teardown/) - end - - should "auto-verify even if user teardown explodes" do - @teardown_code =<<-EOM - def teardown - raise "self destruct" - end - EOM - @expect_errors = 2 - write_and_execute_test - assert_output_contains(/self destruct/) - end - - it "plays nice with inherited teardown methods" do - @full_code ||=<<-EOTEST - require File.expand_path(File.dirname(__FILE__) + "/../test_helper") - require 'hardmock' - class Test::Unit::TestCase - def teardown - puts "Test helper teardown" - end - end - class DummyTest < Test::Unit::TestCase - def test_prepare_to_die - create_mock :automobile - @automobile.expects.start - end - end - EOTEST - write_and_execute_test - assert_output_contains(/Test helper teardown/) - end - - # - # HELPERS - # - - def temp_test_file - File.expand_path(File.dirname(__FILE__) + "/tear_down_verification_test.rb") - end - - def run_test(tbody) - File.open(temp_test_file,"w") { |f| f.print(tbody) } - @test_output = `ruby #{temp_test_file} 2>&1` - end - - def formatted_test_output - if @test_output - @test_output.split(/\n/).map { |line| "> #{line}" }.join("\n") - else - "(NO TEST OUTPUT!)" - end - end - - def remove_temp_test_file - FileUtils::rm_f temp_test_file - end - - def assert_results(h) - if @test_output !~ /#{h[:tests]} tests, [0-9]+ assertions, #{h[:failures]} failures, #{h[:errors]} errors/ - flunk "Test results didn't match #{h.inspect}:\n#{formatted_test_output}" - end - end - - def assert_output_contains(*patterns) - patterns.each do |pattern| - if @test_output !~ pattern - flunk "Test output didn't match #{pattern.inspect}:\n#{formatted_test_output}" - end - end - end - - def assert_output_doesnt_contain(*patterns) - patterns.each do |pattern| - assert @test_output !~ pattern, "Output shouldn't match #{pattern.inspect} but it does." - end - end - - def write_and_execute_test - @test_code ||=<<-EOM - def test_setup_doomed_expectation - create_mock :automobile - @automobile.expects.start - end - EOM - @full_code ||=<<-EOTEST - require File.expand_path(File.dirname(__FILE__) + "/../test_helper") - require 'hardmock' - class DummyTest < Test::Unit::TestCase - #{@teardown_code} - #{@test_code} - end - EOTEST - run_test @full_code - - if @expect_unmet_expectations - assert_output_contains(/unmet expectations/i, /automobile/, /start/) - else - assert_output_doesnt_contain(/unmet expectations/i, /automobile/, /start/) - end - - @expect_tests ||= 1 - @expect_failures ||= 0 - @expect_errors ||= 1 - assert_results :tests => @expect_tests, :failures => @expect_failures, :errors => @expect_errors - end - -end diff --git a/vendor/hardmock/test/functional/direct_mock_usage_test.rb b/vendor/hardmock/test/functional/direct_mock_usage_test.rb deleted file mode 100644 index 4bdc5b5f..00000000 --- a/vendor/hardmock/test/functional/direct_mock_usage_test.rb +++ /dev/null @@ -1,404 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock' - -class DirectMockUsageTest < Test::Unit::TestCase - - def setup - @bird = Mock.new('bird') - end - - def teardown - end - - # - # TESTS - # - - it "raises VerifyError if expected method not called" do - @bird.expects.flap_flap - - err = assert_raise VerifyError do - @bird._verify - end - assert_match(/unmet expectations/i, err.message) - end - - should "not raise when expected calls are made in order" do - @bird.expects.flap_flap - @bird.expects.bang - @bird.expects.plop - - @bird.flap_flap - @bird.bang - @bird.plop - - @bird._verify - end - - it "raises ExpectationError when unexpected method are called" do - @bird.expects.flap_flap - - err = assert_raise ExpectationError do - @bird.shoot - end - assert_match(/wrong method/i, err.message) - end - - it "raises ExpectationError on bad arguments" do - @bird.expects.flap_flap(:swoosh) - - err = assert_raise ExpectationError do - @bird.flap_flap(:rip) - end - assert_match(/wrong arguments/i, err.message) - end - - it "raises VerifyError when not all expected methods are called" do - @bird.expects.flap_flap - @bird.expects.bang - @bird.expects.plop - - @bird.flap_flap - - err = assert_raise VerifyError do - @bird._verify - end - assert_match(/unmet expectations/i, err.message) - end - - it "raises ExpectationError when calls are made out of order" do - @bird.expects.flap_flap - @bird.expects.bang - @bird.expects.plop - - @bird.flap_flap - err = assert_raise ExpectationError do - @bird.plop - end - assert_match(/wrong method/i, err.message) - end - - it "returns the configured value" do - @bird.expects.plop.returns(':P') - assert_equal ':P', @bird.plop - @bird._verify - - @bird.expects.plop.returns(':x') - assert_equal ':x', @bird.plop - @bird._verify - end - - it "returns nil when no return is specified" do - @bird.expects.plop - assert_nil @bird.plop - @bird._verify - end - - it "raises the configured exception" do - err = RuntimeError.new('shaq') - @bird.expects.plop.raises(err) - actual_err = assert_raise RuntimeError do - @bird.plop - end - assert_same err, actual_err, 'should be the same error' - @bird._verify - end - - it "raises a RuntimeError when told to 'raise' a string" do - @bird.expects.plop.raises('shaq') - err = assert_raise RuntimeError do - @bird.plop - end - assert_match(/shaq/i, err.message) - @bird._verify - end - - it "raises a default RuntimeError" do - @bird.expects.plop.raises - err = assert_raise RuntimeError do - @bird.plop - end - assert_match(/error/i, err.message) - @bird._verify - end - - it "is quiet when correct arguments given" do - thing = Object.new - @bird.expects.plop(:big,'one',thing) - @bird.plop(:big,'one',thing) - @bird._verify - end - - it "raises ExpectationError when wrong number of arguments specified" do - thing = Object.new - @bird.expects.plop(:big,'one',thing) - err = assert_raise ExpectationError do - # more - @bird.plop(:big,'one',thing,:other) - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - - @bird.expects.plop(:big,'one',thing) - err = assert_raise ExpectationError do - # less - @bird.plop(:big,'one') - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - - @bird.expects.plop - err = assert_raise ExpectationError do - # less - @bird.plop(:big) - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - end - - it "raises ExpectationError when arguments don't match" do - thing = Object.new - @bird.expects.plop(:big,'one',thing) - err = assert_raise ExpectationError do - @bird.plop(:big,'two',thing,:other) - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - end - - it "can use a block for custom reactions" do - mitt = nil - @bird.expects.plop { mitt = :ball } - assert_nil mitt - @bird.plop - assert_equal :ball, mitt, 'didnt catch the ball' - @bird._verify - - @bird.expects.plop { raise 'ball' } - err = assert_raise RuntimeError do - @bird.plop - end - assert_match(/ball/i, err.message) - @bird._verify - end - - it "passes mock-call arguments to the expectation block" do - ball = nil - mitt = nil - @bird.expects.plop {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - @bird.plop(:ball,:mitt) - assert_equal :ball, ball - assert_equal :mitt, mitt - @bird._verify - end - - it "validates arguments if specified in addition to a block" do - ball = nil - mitt = nil - @bird.expects.plop(:ball,:mitt) {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - @bird.plop(:ball,:mitt) - assert_equal :ball, ball - assert_equal :mitt, mitt - @bird._verify - - ball = nil - mitt = nil - @bird.expects.plop(:bad,:stupid) {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - err = assert_raise ExpectationError do - @bird.plop(:ball,:mitt) - end - assert_match(/wrong arguments/i, err.message) - assert_nil ball - assert_nil mitt - @bird._verify - - ball = nil - mitt = nil - @bird.expects.plop(:ball,:mitt) {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - err = assert_raise ExpectationError do - @bird.plop(:ball) - end - assert_match(/wrong arguments/i, err.message) - assert_nil ball - assert_nil mitt - @bird._verify - end - - it "passes runtime blocks to the expectation block as the final argument" do - runtime_block_called = false - got_arg = nil - - # Eg, bird expects someone to subscribe to :tweet using the 'when' method - @bird.expects.when(:tweet) { |arg1, block| - got_arg = arg1 - block.call - } - - @bird.when(:tweet) do - runtime_block_called = true - end - - assert_equal :tweet, got_arg, "Wrong arg" - assert runtime_block_called, "The runtime block should have been invoked by the user block" - - @bird.expects.when(:warnk) { |e,blk| } - - err = assert_raise ExpectationError do - @bird.when(:honk) { } - end - assert_match(/wrong arguments/i, err.message) - - @bird._verify - end - - it "passes the runtime block to the expectation block as sole argument if no other args come into play" do - runtime_block_called = false - @bird.expects.subscribe { |block| block.call } - @bird.subscribe do - runtime_block_called = true - end - assert runtime_block_called, "The runtime block should have been invoked by the user block" - end - - it "provides nil as final argument if expectation block seems to want a block" do - invoked = false - @bird.expects.kablam(:scatter) { |shot,block| - assert_equal :scatter, shot, "Wrong shot" - assert_nil block, "The expectation block should get a nil block when user neglects to pass one" - invoked = true - } - @bird.kablam :scatter - assert invoked, "Expectation block not invoked" - - @bird._verify - end - - it "can set explicit return after an expectation block" do - got = nil - @bird.expects.kablam(:scatter) { |shot| - got = shot - }.returns(:death) - - val = @bird.kablam :scatter - assert_equal :death, val, "Wrong return value" - assert_equal :scatter, got, "Wrong argument" - @bird._verify - end - - it "can raise after an expectation block" do - got = nil - @bird.expects.kablam(:scatter) do |shot| - got = shot - end.raises "hell" - - err = assert_raise RuntimeError do - @bird.kablam :scatter - end - assert_match(/hell/i, err.message) - - @bird._verify - end - - it "stores the semantic value of the expectation block after it executes" do - expectation = @bird.expects.kablam(:slug) { |shot| - "The shot was #{shot}" - } - - assert_not_nil expectation, "Expectation nil" - assert_nil expectation.block_value, "Block value should start out nil" - - ret_val = @bird.kablam :slug - - assert_equal "The shot was slug", expectation.block_value - assert_equal "The shot was slug", ret_val, "Block value should also be used for return" - - @bird._verify - end - - - it "uses the value of the expectation block as the default return value" do - @bird.expects.kablam(:scatter) { |shot| - "The shot was #{shot}" - } - val = @bird.kablam :scatter - assert_equal "The shot was scatter", val, "Wrong return value" - @bird._verify - end - - it "returns the Expectation even if 'returns' is used" do - expectation = @bird.expects.kablam(:slug) { |shot| - "The shot was #{shot}" - }.returns :hosed - - assert_not_nil expectation, "Expectation nil" - assert_nil expectation.block_value, "Block value should start out nil" - - ret_val = @bird.kablam :slug - - assert_equal "The shot was slug", expectation.block_value - assert_equal :hosed, ret_val, "Block value should also be used for return" - - @bird._verify - end - - it "returns the Expectation even if 'raises' is used" do - expectation = @bird.expects.kablam(:slug) { |shot| - "The shot was #{shot}" - }.raises "aiee!" - - assert_not_nil expectation, "Expectation nil" - assert_nil expectation.block_value, "Block value should start out nil" - - err = assert_raise RuntimeError do - @bird.kablam :slug - end - assert_match(/aiee!/i, err.message) - assert_equal "The shot was slug", expectation.block_value - @bird._verify - end - - - it "supports assignment-style methods" do - @bird.expects.size = "large" - @bird.size = "large" - @bird._verify - end - - it "supports assignments and raising (using explicit-method syntax)" do - @bird.expects('size=','large').raises "boom" - - err = assert_raise RuntimeError do - @bird.size = "large" - end - assert_match(/boom/i, err.message) - end - -end diff --git a/vendor/hardmock/test/functional/hardmock_test.rb b/vendor/hardmock/test/functional/hardmock_test.rb deleted file mode 100644 index a1bf2a67..00000000 --- a/vendor/hardmock/test/functional/hardmock_test.rb +++ /dev/null @@ -1,442 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock' -require 'assert_error' - -class HardmockTest < Test::Unit::TestCase - - # - # TESTS - # - - it "conveniently creates mocks using create_mock and create_mocks" do - - h = create_mock :donkey - assert_equal [ :donkey ], h.keys - - assert_mock_exists :donkey - assert_same @donkey, h[:donkey] - - assert_equal [ :donkey ], @all_mocks.keys, "Wrong keyset for @all_mocks" - - h2 = create_mocks :cat, 'dog' # symbol/string indifference at this level - assert_equal [:cat,:dog].to_set, h2.keys.to_set, "Wrong keyset for second hash" - assert_equal [:cat,:dog,:donkey].to_set, @all_mocks.keys.to_set, "@all_mocks wrong" - - assert_mock_exists :cat - assert_same @cat, h2[:cat] - assert_mock_exists :dog - assert_same @dog, h2[:dog] - - assert_mock_exists :donkey - end - - it "provides literal 'expects' syntax" do - assert_nil @order, "Should be no @order yet" - create_mock :order - assert_not_nil @order, "@order should be built" - - # Setup an expectation - @order.expects.update_stuff :key1 => 'val1', :key2 => 'val2' - - # Use the mock - @order.update_stuff :key1 => 'val1', :key2 => 'val2' - - # Verify - verify_mocks - - # See that it's ok to do it again - verify_mocks - end - - it "supports 'with' for specifying argument expectations" do - create_mocks :car - @car.expects(:fill).with('gas','booze') - @car.fill('gas', 'booze') - verify_mocks - end - - it "supports several mocks at once" do - create_mocks :order_builder, :order, :customer - - @order_builder.expects.create_new_order.returns @order - @customer.expects.account_number.returns(1234) - @order.expects.account_no = 1234 - @order.expects.save! - - # Run "the code" - o = @order_builder.create_new_order - o.account_no = @customer.account_number - o.save! - - verify_mocks - end - - it "enforces inter-mock call ordering" do - create_mocks :order_builder, :order, :customer - - @order_builder.expects.create_new_order.returns @order - @customer.expects.account_number.returns(1234) - @order.expects.account_no = 1234 - @order.expects.save! - - # Run "the code" - o = @order_builder.create_new_order - err = assert_raise ExpectationError do - o.save! - end - assert_match(/wrong object/i, err.message) - assert_match(/order.save!/i, err.message) - assert_match(/customer.account_number/i, err.message) - - assert_error VerifyError, /unmet expectations/i do - verify_mocks - end - end - - class UserPresenter - def initialize(args) - view = args[:view] - model = args[:model] - model.when :data_changes do - view.user_name = model.user_name - end - view.when :user_edited do - model.user_name = view.user_name - end - end - end - - it "makes MVP testing simple" do - mox = create_mocks :model, :view - - data_change = @model.expects.when(:data_changes) { |evt,block| block } - user_edit = @view.expects.when(:user_edited) { |evt,block| block } - - UserPresenter.new mox - - # Expect user name transfer from model to view - @model.expects.user_name.returns 'Da Croz' - @view.expects.user_name = 'Da Croz' - # Trigger data change event in model - data_change.block_value.call - - # Expect user name transfer from view to model - @view.expects.user_name.returns '6:8' - @model.expects.user_name = '6:8' - # Trigger edit event in view - user_edit.block_value.call - - verify_mocks - end - - it "continues to function after verify, if verification error is controlled" do - mox = create_mocks :model, :view - data_change = @model.expects.when(:data_changes) { |evt,block| block } - user_edit = @view.expects.when(:user_edited) { |evt,block| block } - UserPresenter.new mox - - # Expect user name transfer from model to view - @model.expects.user_name.returns 'Da Croz' - @view.expects.user_name = 'Da Croz' - - assert_error ExpectationError, /model.monkey_wrench/i do - @model.monkey_wrench - end - - # This should raise because of unmet expectations - assert_error VerifyError, /unmet expectations/i, /user_name/i do - verify_mocks - end - - # See that the non-forced verification remains quiet - assert_nothing_raised VerifyError do - verify_mocks(false) - end - - @model.expects.never_gonna_happen - - assert_error VerifyError, /unmet expectations/i, /never_gonna_happen/i do - verify_mocks - end - end - - class UserPresenterBroken - def initialize(args) - view = args[:view] - model = args[:model] - model.when :data_changes do - view.user_name = model.user_name - end - # no view stuff, will break appropriately - end - end - - it "flunks for typical Presenter constructor wiring failure" do - mox = create_mocks :model, :view - - data_change = @model.expects.when(:data_changes) { |evt,block| block } - user_edit = @view.expects.when(:user_edited) { |evt,block| block } - - UserPresenterBroken.new mox - - err = assert_raise VerifyError do - verify_mocks - end - assert_match(/unmet expectations/i, err.message) - assert_match(/view.when\(:user_edited\)/i, err.message) - - end - - it "provides convenient event-subscription trap syntax for MVP testing" do - mox = create_mocks :model, :view - - data_change = @model.trap.when(:data_changes) - user_edit = @view.trap.when(:user_edited) - - UserPresenter.new mox - - # Expect user name transfer from model to view - @model.expects.user_name.returns 'Da Croz' - @view.expects.user_name = 'Da Croz' - # Trigger data change event in model - data_change.trigger - - # Expect user name transfer from view to model - @view.expects.user_name.returns '6:8' - @model.expects.user_name = '6:8' - # Trigger edit event in view - user_edit.trigger - - verify_mocks - end - - it "raises if you try to pass an expectation block to 'trap'" do - create_mock :model - assert_error Hardmock::ExpectationError, /blocks/i, /trap/i do - @model.trap.when(:some_event) do raise "huh?" end - end - end - - class Grinder - def initialize(objects) - @chute = objects[:chute] - @bucket = objects[:bucket] - @blade = objects[:blade] - end - - def grind(slot) - @chute.each_bean(slot) do |bean| - @bucket << @blade.chop(bean) - end - end - end - - it "lets you write clear iteration-oriented expectations" do - grinder = Grinder.new create_mocks(:blade, :chute, :bucket) - - # Style 1: assertions on method args is done explicitly in block - @chute.expects.each_bean { |slot,block| - assert_equal :side_slot, slot, "Wrong slot" - block.call :bean1 - block.call :bean2 - } - - @blade.expects.chop(:bean1).returns(:grounds1) - @bucket.expects('<<', :grounds1) - - @blade.expects.chop(:bean2).returns(:grounds2) - @bucket.expects('<<', :grounds2) - - # Run "the code" - grinder.grind(:side_slot) - - verify_mocks - - # Style 2: assertions on method arguments done implicitly in the expectation code - @chute.expects.each_bean(:main_slot) { |slot,block| - block.call :bean3 - } - @blade.expects.chop(:bean3).returns(:grounds3) - @bucket.expects('<<', :grounds3) - grinder.grind :main_slot - verify_mocks - end - - it "further supports iteration testing using 'yield'" do - grinder = Grinder.new create_mocks(:blade, :chute, :bucket) - - @chute.expects.each_bean(:side_slot).yields :bean1, :bean2 - - @blade.expects.chop(:bean1).returns(:grounds1) - @bucket.expects('<<', :grounds1) - - @blade.expects.chop(:bean2).returns(:grounds2) - @bucket.expects('<<', :grounds2) - - grinder.grind :side_slot - - verify_mocks - end - - class HurtLocker - attr_reader :caught - def initialize(opts) - @locker = opts[:locker] - @store = opts[:store] - end - - def do_the_thing(area,data) - @locker.with_lock(area) do - @store.eat(data) - end - rescue => oops - @caught = oops - end - end - - it "makes mutex-style locking scenarios easy to test" do - hurt = HurtLocker.new create_mocks(:locker, :store) - - @locker.expects.with_lock(:main).yields - @store.expects.eat("some info") - - hurt.do_the_thing(:main, "some info") - - verify_mocks - end - - it "makes it easy to simulate error in mutex-style locking scenarios" do - hurt = HurtLocker.new create_mocks(:locker, :store) - err = StandardError.new('fmshooop') - @locker.expects.with_lock(:main).yields - @store.expects.eat("some info").raises(err) - - hurt.do_the_thing(:main, "some info") - - assert_same err, hurt.caught, "Expected that error to be handled internally" - verify_mocks - end - - it "actually returns 'false' instead of nil when mocking boolean return values" do - create_mock :car - @car.expects.ignition_on?.returns(true) - assert_equal true, @car.ignition_on?, "Should be true" - @car.expects.ignition_on?.returns(false) - assert_equal false, @car.ignition_on?, "Should be false" - end - - it "can mock most methods inherited from object using literal syntax" do - target_methods = %w|id clone display dup eql? ==| - create_mock :foo - target_methods.each do |m| - eval %{@foo.expects(m, "some stuff")} - eval %{@foo.#{m} "some stuff"} - end - end - - it "provides 'expect' as an alias for 'expects'" do - create_mock :foo - @foo.expect.boomboom - @foo.boomboom - verify_mocks - end - - it "provides 'should_receive' as an alias for 'expects'" do - create_mock :foo - @foo.should_receive.boomboom - @foo.boomboom - verify_mocks - end - - it "provides 'and_return' as an alias for 'returns'" do - create_mock :foo - @foo.expects(:boomboom).and_return :brick - assert_equal :brick, @foo.boomboom - verify_mocks - end - - it "does not interfere with a core subset of Object methods" do - create_mock :foo - @foo.method(:inspect) - @foo.inspect - @foo.to_s - @foo.instance_variables - @foo.instance_eval("") - verify_mocks - end - - it "can raise errors from within an expectation block" do - create_mock :cat - @cat.expects.meow do |arg| - assert_equal "mix", arg - raise 'HAIRBALL' - end - assert_error RuntimeError, 'HAIRBALL' do - @cat.meow("mix") - end - end - - it "can raise errors AFTER an expectation block" do - create_mock :cat - @cat.expects.meow do |arg| - assert_equal "mix", arg - end.raises('HAIRBALL') - assert_error RuntimeError, 'HAIRBALL' do - @cat.meow("mix") - end - end - - it "raises an immediate error if a mock is created with a nil name (common mistake: create_mock @cat)" do - # I make this mistake all the time: Typing in an instance var name instead of a symbol in create_mocks. - # When you do that, you're effectively passing nil(s) in as mock names. - assert_error ArgumentError, /'nil' is not a valid name for a mock/ do - create_mocks @apples, @oranges - end - end - - it "overrides 'inspect' to make nice output" do - create_mock :hay_bailer - assert_equal "", @hay_bailer.inspect, "Wrong output from 'inspect'" - end - - it "raises if prepare_hardmock_control is invoked after create_mocks, or more than once" do - create_mock :hi_there - create_mocks :another, :one - assert_error RuntimeError, /already setup/ do - prepare_hardmock_control - end - end - - should "support alias verify_hardmocks" do - create_mock :tree - @tree.expects(:grow) - assert_error VerifyError, /unmet/i do - verify_hardmocks - end - end - - # - # HELPERS - # - - def assert_mock_exists(name) - assert_not_nil @all_mocks, "@all_mocks not here yet" - mo = @all_mocks[name] - assert_not_nil mo, "Mock '#{name}' not in @all_mocks" - assert_kind_of Mock, mo, "Wrong type of object, wanted a Mock" - assert_equal name.to_s, mo._name, "Mock '#{name}' had wrong name" - ivar = self.instance_variable_get("@#{name}") - assert_not_nil ivar, "Mock '#{name}' not set as ivar" - assert_same mo, ivar, "Mock '#{name}' ivar not same as instance in @all_mocks" - assert_same @main_mock_control, mo._control, "Mock '#{name}' doesn't share the main mock control" - end -end - diff --git a/vendor/hardmock/test/functional/stubbing_test.rb b/vendor/hardmock/test/functional/stubbing_test.rb deleted file mode 100644 index 2367ffcd..00000000 --- a/vendor/hardmock/test/functional/stubbing_test.rb +++ /dev/null @@ -1,487 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock' -require 'assert_error' - -class StubbingTest < Test::Unit::TestCase - - # - # TESTS - # - - it "stubs a class method (and un-stubs after reset_stubs)" do - assert_equal "stones and gravel", Concrete.pour - assert_equal "glug glug", Jug.pour - - Concrete.stubs!(:pour).returns("dust and plaster") - - 3.times do - assert_equal "dust and plaster", Concrete.pour - end - - assert_equal "glug glug", Jug.pour, "Jug's 'pour' method broken" - assert_equal "stones and gravel", Concrete._hardmock_original_pour, "Original 'pour' method not aliased" - - assert_equal "For roads", Concrete.describe, "'describe' method broken" - - reset_stubs - - assert_equal "stones and gravel", Concrete.pour, "'pour' method not restored" - assert_equal "For roads", Concrete.describe, "'describe' method broken after verify" - - end - - it "stubs several class methods" do - Concrete.stubs!(:pour).returns("sludge") - Concrete.stubs!(:describe).returns("awful") - Jug.stubs!(:pour).returns("milk") - - assert_equal "sludge", Concrete.pour - assert_equal "awful", Concrete.describe - assert_equal "milk", Jug.pour - - reset_stubs - - assert_equal "stones and gravel", Concrete.pour - assert_equal "For roads", Concrete.describe - assert_equal "glug glug", Jug.pour - end - - it "stubs instance methods" do - slab = Concrete.new - assert_equal "bonk", slab.hit - - slab.stubs!(:hit).returns("slap") - assert_equal "slap", slab.hit, "'hit' not stubbed" - - reset_stubs - - assert_equal "bonk", slab.hit, "'hit' not restored" - end - - it "stubs instance methods without breaking class methods or other instances" do - slab = Concrete.new - scrape = Concrete.new - assert_equal "an instance", slab.describe - assert_equal "an instance", scrape.describe - assert_equal "For roads", Concrete.describe - - slab.stubs!(:describe).returns("new instance describe") - assert_equal "new instance describe", slab.describe, "'describe' on instance not stubbed" - assert_equal "an instance", scrape.describe, "'describe' on 'scrape' instance broken" - assert_equal "For roads", Concrete.describe, "'describe' class method broken" - - reset_stubs - - assert_equal "an instance", slab.describe, "'describe' instance method not restored" - assert_equal "an instance", scrape.describe, "'describe' on 'scrape' instance broken after restore" - assert_equal "For roads", Concrete.describe, "'describe' class method broken after restore" - end - - should "allow stubbing of nonexistant class methods" do - Concrete.stubs!(:funky).returns('juice') - assert_equal 'juice', Concrete.funky - end - - should "allow stubbing of nonexistant instance methods" do - chunk = Concrete.new - chunk.stubs!(:shark).returns('bite') - assert_equal 'bite', chunk.shark - end - - should "allow re-stubbing" do - Concrete.stubs!(:pour).returns("one") - assert_equal "one", Concrete.pour - - Concrete.stubs!(:pour).raises("hell") - assert_error RuntimeError, /hell/ do - Concrete.pour - end - - Concrete.stubs!(:pour).returns("two") - assert_equal "two", Concrete.pour - - reset_stubs - - assert_equal "stones and gravel", Concrete.pour - end - - it "does nothing with a runtime block when simply stubbing" do - slab = Concrete.new - slab.stubs!(:hit) do |nothing| - raise "BOOOMM!" - end - slab.hit - reset_stubs - end - - it "can raise errors from a stubbed method" do - Concrete.stubs!(:pour).raises(StandardError.new("no!")) - assert_error StandardError, /no!/ do - Concrete.pour - end - end - - it "provides string syntax for convenient raising of RuntimeErrors" do - Concrete.stubs!(:pour).raises("never!") - assert_error RuntimeError, /never!/ do - Concrete.pour - end - end - - - # - # Per-method mocking on classes or instances - # - - it "mocks specific methods on existing classes, and returns the class method to normal after verification" do - - assert_equal "stones and gravel", Concrete.pour, "Concrete.pour is already messed up" - - Concrete.expects!(:pour).returns("ALIGATORS") - assert_equal "ALIGATORS", Concrete.pour - - verify_mocks - assert_equal "stones and gravel", Concrete.pour, "Concrete.pour not restored" - end - - it "flunks if expected class method is not invoked" do - - Concrete.expects!(:pour).returns("ALIGATORS") - assert_error(Hardmock::VerifyError, /Concrete.pour/, /unmet expectations/i) do - verify_mocks - end - clear_expectations - end - - it "supports all normal mock functionality for class methods" do - - Concrete.expects!(:pour, "two tons").returns("mice") - Concrete.expects!(:pour, "three tons").returns("cats") - Concrete.expects!(:pour, "four tons").raises("Can't do it") - Concrete.expects!(:pour) do |some, args| - "==#{some}+#{args}==" - end - - assert_equal "mice", Concrete.pour("two tons") - assert_equal "cats", Concrete.pour("three tons") - assert_error(RuntimeError, /Can't do it/) do - Concrete.pour("four tons") - end - assert_equal "==first+second==", Concrete.pour("first","second") - end - - - it "enforces inter-mock ordering when mocking class methods" do - create_mocks :truck, :foreman - - @truck.expects.backup - Concrete.expects!(:pour, "something") - @foreman.expects.shout - - @truck.backup - assert_error Hardmock::ExpectationError, /wrong/i, /expected call/i, /Concrete.pour/ do - @foreman.shout - end - assert_error Hardmock::VerifyError, /unmet expectations/i, /foreman.shout/ do - verify_mocks - end - clear_expectations - end - - should "allow mocking non-existant class methods" do - Concrete.expects!(:something).returns("else") - assert_equal "else", Concrete.something - end - - it "mocks specific methods on existing instances, then restore them after verify" do - - slab = Concrete.new - assert_equal "bonk", slab.hit - - slab.expects!(:hit).returns("slap") - assert_equal "slap", slab.hit, "'hit' not stubbed" - - verify_mocks - assert_equal "bonk", slab.hit, "'hit' not restored" - end - - it "flunks if expected instance method is not invoked" do - - slab = Concrete.new - slab.expects!(:hit) - - assert_error Hardmock::VerifyError, /unmet expectations/i, /Concrete.hit/ do - verify_mocks - end - clear_expectations - end - - it "supports all normal mock functionality for instance methods" do - - slab = Concrete.new - - slab.expects!(:hit, "soft").returns("hey") - slab.expects!(:hit, "hard").returns("OOF") - slab.expects!(:hit).raises("stoppit") - slab.expects!(:hit) do |some, args| - "==#{some}+#{args}==" - end - - assert_equal "hey", slab.hit("soft") - assert_equal "OOF", slab.hit("hard") - assert_error(RuntimeError, /stoppit/) do - slab.hit - end - assert_equal "==first+second==", slab.hit("first","second") - - end - - it "enforces inter-mock ordering when mocking instance methods" do - create_mocks :truck, :foreman - slab1 = Concrete.new - slab2 = Concrete.new - - @truck.expects.backup - slab1.expects!(:hit) - @foreman.expects.shout - slab2.expects!(:hit) - @foreman.expects.whatever - - @truck.backup - slab1.hit - @foreman.shout - assert_error Hardmock::ExpectationError, /wrong/i, /expected call/i, /Concrete.hit/ do - @foreman.whatever - end - assert_error Hardmock::VerifyError, /unmet expectations/i, /foreman.whatever/ do - verify_mocks - end - clear_expectations - end - - should "allow mocking non-existant instance methods" do - slab = Concrete.new - slab.expects!(:wholly).returns('happy') - assert_equal 'happy', slab.wholly - end - - should "support concrete expectations that deal with runtime blocks" do - - Concrete.expects!(:pour, "a lot") do |how_much, block| - assert_equal "a lot", how_much, "Wrong how_much arg" - assert_not_nil block, "nil runtime block" - assert_equal "the block value", block.call, "Wrong runtime block value" - end - - Concrete.pour("a lot") do - "the block value" - end - - end - - it "can stub methods on mock objects" do - create_mock :horse - @horse.stubs!(:speak).returns("silence") - @horse.stubs!(:hello).returns("nothing") - @horse.expects(:canter).returns("clip clop") - - assert_equal "silence", @horse.speak - assert_equal "clip clop", @horse.canter - assert_equal "silence", @horse.speak - assert_equal "silence", @horse.speak - assert_equal "nothing", @horse.hello - assert_equal "nothing", @horse.hello - - verify_mocks - reset_stubs - end - - it "can stub the new method and return values" do - Concrete.stubs!(:new).returns("this value") - assert_equal "this value", Concrete.new, "did not properly stub new class method" - reset_stubs - end - - it "can mock the new method and return values" do - Concrete.expects!(:new).with("foo").returns("hello") - Concrete.expects!(:new).with("bar").returns("world") - - assert_equal "hello", Concrete.new("foo"), "did not properly mock out new class method" - assert_equal "world", Concrete.new("bar"), "did not properly mock out new class method" - - verify_mocks - reset_stubs - end - - it "can mock several different class methods at once" do - sim_code = lambda do |input| - record = Multitool.find_record(input) - report = Multitool.generate_report(record) - Multitool.format_output(report) - end - - @identifier = "the id" - @record = "the record" - @report = "the report" - @output = "the output" - - Multitool.expects!(:find_record).with(@identifier).returns(@record) - Multitool.expects!(:generate_report).with(@record).returns(@report) - Multitool.expects!(:format_output).with(@report).returns(@output) - - result = sim_code.call(@identifier) - assert_equal @output, result, "Wrong output" - end - - it "can handle a mix of different and repeat class method mock calls" do - prep = lambda { - Multitool.expects!(:find_record).with("A").returns("1") - Multitool.expects!(:generate_report).with("1") - Multitool.expects!(:find_record).with("B").returns("2") - Multitool.expects!(:generate_report).with("2") - } - - prep[] - Multitool.generate_report(Multitool.find_record("A")) - Multitool.generate_report(Multitool.find_record("B")) - - prep[] - Multitool.generate_report(Multitool.find_record("A")) - assert_error Hardmock::ExpectationError, /Wrong arguments/, /find_record\("B"\)/, /find_record\("C"\)/ do - Multitool.generate_report(Multitool.find_record("C")) - end - clear_expectations - end - - it "can mock several concrete instance methods at once" do - inst = OtherMultitool.new - sim_code = lambda do |input| - record = inst.find_record(input) - report = inst.generate_report(record) - inst.format_output(report) - end - - @identifier = "the id" - @record = "the record" - @report = "the report" - @output = "the output" - - inst.expects!(:find_record).with(@identifier).returns(@record) - inst.expects!(:generate_report).with(@record).returns(@report) - inst.expects!(:format_output).with(@report).returns(@output) - - result = sim_code.call(@identifier) - assert_equal @output, result, "Wrong output" - end - - it "verifies all concrete expects! from several different expectations" do - Multitool.expects!(:find_record) - Multitool.expects!(:generate_report) - Multitool.expects!(:format_output) - - Multitool.find_record - Multitool.generate_report - - assert_error Hardmock::VerifyError, /unmet expectations/i, /format_output/i do - verify_mocks - end - end - - it "will not allow expects! to be used on a mock object" do - create_mock :cow - assert_error Hardmock::StubbingError, /expects!/, /mock/i, /something/ do - @cow.expects!(:something) - end - end - - it "does not allow stubbing on nil objects" do - [ nil, @this_is_nil ].each do |nil_obj| - assert_error Hardmock::StubbingError, /cannot/i, /nil/i, /intentionally/ do - nil_obj.stubs!(:wont_work) - end - end - end - - it "does not allow concrete method mocking on nil objects" do - [ nil, @this_is_nil ].each do |nil_obj| - assert_error Hardmock::StubbingError, /cannot/i, /nil/i, /intentionally/ do - nil_obj.expects!(:wont_work) - end - end - end - - it "provides an alternate method for stubbing on nil objects" do - @this_is_nil.intentionally_stubs!(:bogus).returns('output') - assert_equal 'output', @this_is_nil.bogus - end - - it "provides an alternate method for mocking concreate methods on nil objects" do - @this_is_nil.intentionally_expects!(:bogus).returns('output') - assert_error Hardmock::VerifyError, /unmet expectations/i, /NilClass.bogus/ do - verify_mocks - end - end - - # - # HELPERS - # - - class Concrete - def initialize; end - def self.pour - "stones and gravel" - end - - def self.describe - "For roads" - end - - def hit - "bonk" - end - - def describe - "an instance" - end - end - - class Jug - def self.pour - "glug glug" - end - end - - class Multitool - def self.find_record(*a) - raise "The real Multitool.find_record was called with #{a.inspect}" - end - def self.generate_report(*a) - raise "The real Multitool.generate_report was called with #{a.inspect}" - end - def self.format_output(*a) - raise "The real Multitool.format_output was called with #{a.inspect}" - end - end - - class OtherMultitool - def find_record(*a) - raise "The real OtherMultitool#find_record was called with #{a.inspect}" - end - def generate_report(*a) - raise "The real OtherMultitool#generate_report was called with #{a.inspect}" - end - def format_output(*a) - raise "The real OtherMultitool#format_output was called with #{a.inspect}" - end - end - -end - diff --git a/vendor/hardmock/test/test_helper.rb b/vendor/hardmock/test/test_helper.rb deleted file mode 100644 index 9b2b0d36..00000000 --- a/vendor/hardmock/test/test_helper.rb +++ /dev/null @@ -1,51 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -here = File.expand_path(File.dirname(__FILE__)) -$: << here - -require "#{here}/../config/environment" -require 'test/unit' -require 'fileutils' -require 'logger' -require 'find' -require 'yaml' -require 'set' -require 'ostruct' - -class Test::Unit::TestCase - include FileUtils - - def poll(time_limit) - (time_limit * 10).to_i.times do - return true if yield - sleep 0.1 - end - return false - end - - def self.it(str, &block) - make_test_case "it", str, &block - end - - def self.should(str, &block) - make_test_case "should", str, &block - end - - def self.make_test_case(prefix, str, &block) - tname = self.name.sub(/Test$/,'') - if block - define_method "test #{prefix} #{str}" do - instance_eval &block - end - else - puts ">>> UNIMPLEMENTED CASE: #{tname}: #{str}" - end - end - -end diff --git a/vendor/hardmock/test/unit/expectation_builder_test.rb b/vendor/hardmock/test/unit/expectation_builder_test.rb deleted file mode 100644 index f64d8c31..00000000 --- a/vendor/hardmock/test/unit/expectation_builder_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/expectation_builder' - -class ExpectationBuilderTest < Test::Unit::TestCase - include Hardmock - - def test_build_expectation - builder = ExpectationBuilder.new - - ex = builder.build_expectation( :stuff => 'inside' ) - assert_not_nil ex, "Didn't build an expectation" - assert_kind_of Expectation, ex, "Wrong type!" - - # Shhhh... fragile, yes, whatever. The functional tests do the - # real testing of this anyway - assert_equal({:stuff => 'inside'}, ex.instance_variable_get('@options'), "Hash not sent to SimpleExpectation constructor") - end - -end diff --git a/vendor/hardmock/test/unit/expectation_test.rb b/vendor/hardmock/test/unit/expectation_test.rb deleted file mode 100644 index bf04de5a..00000000 --- a/vendor/hardmock/test/unit/expectation_test.rb +++ /dev/null @@ -1,380 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/expectation' -require 'hardmock/errors' -require 'assert_error' - -class ExpectationTest < Test::Unit::TestCase - include Hardmock - - def setup - @mock = TheMock.new - end - # - # HELPERS - # - - class TheMock - def _name; 'the_mock'; end - end - class OtherMock - def _name; 'other_mock'; end - end - - # - # TESTS - # - - def test_to_s - ex = Expectation.new( :mock => @mock, :method => 'a_func', :arguments => [1, "two", :three, { :four => 4 }] ) - assert_equal %|the_mock.a_func(1, "two", :three, {:four=>4})|, ex.to_s - end - - def test_apply_method_call - se = Expectation.new(:mock => @mock, :method => 'some_func', - :arguments => [1,'two',:three] ) - - # Try it good: - assert_nothing_raised ExpectationError do - se.apply_method_call( @mock, 'some_func', [1,'two',:three], nil ) - end - - # Bad func name: - err = assert_raise ExpectationError do - se.apply_method_call( @mock, 'wrong_func', [1,'two',:three], nil ) - end - assert_match(/wrong method/i, err.message) - assert_match(/wrong_func/i, err.message) - assert_match(/[1, "two", :three]/i, err.message) - assert_match(/some_func/i, err.message) - assert_match(/the_mock/i, err.message) - - # Wrong mock - err = assert_raise ExpectationError do - se.apply_method_call( OtherMock.new, 'some_func', [1,'two',:three], nil ) - end - assert_match(/[1, "two", :three]/i, err.message) - assert_match(/some_func/i, err.message) - assert_match(/the_mock/i, err.message) - assert_match(/other_mock/i, err.message) - - # Wrong args - err = assert_raise ExpectationError do - se.apply_method_call( @mock, 'some_func', [1,'two',:four], nil) - end - assert_match(/[1, "two", :three]/i, err.message) - assert_match(/[1, "two", :four]/i, err.message) - assert_match(/wrong arguments/i, err.message) - assert_match(/some_func/i, err.message) - end - - def test_apply_method_call_should_call_proc_when_given - # now with a proc - thinger = nil - the_proc = Proc.new { thinger = :shaq } - se = Expectation.new(:mock => @mock, :method => 'some_func', - :block => the_proc) - - # Try it good: - assert_nil thinger - assert_nothing_raised ExpectationError do - se.apply_method_call(@mock, 'some_func', [], nil) - end - assert_equal :shaq, thinger, 'wheres shaq??' - end - - def test_apply_method_call_passes_runtime_block_as_last_argument_to_expectation_block - - passed_block = nil - exp_block_called = false - exp_block = Proc.new { |blk| - exp_block_called = true - passed_block = blk - } - - se = Expectation.new(:mock => @mock, :method => 'some_func', :block => exp_block, - :arguments => []) - - set_flag = false - runtime_block = Proc.new { set_flag = true } - - assert_nil passed_block, "Passed block should be nil" - assert !set_flag, "set_flag should be off" - - # Go - se.apply_method_call( @mock, 'some_func', [], runtime_block) - - # Examine the passed block - assert exp_block_called, "Expectation block not called" - assert_not_nil passed_block, "Should have been passed a block" - assert !set_flag, "set_flag should still be off" - passed_block.call - assert set_flag, "set_flag should be on" - end - - def test_apply_method_call_fails_when_theres_no_expectation_block_to_handle_the_runtime_block - se = Expectation.new(:mock => @mock, :method => 'some_func', :arguments => []) - runtime_block = Proc.new { set_flag = true } - err = assert_raise ExpectationError do - se.apply_method_call( @mock, 'some_func', [], runtime_block) - end - assert_match(/unexpected block/i, err.message) - assert_match(/the_mock.some_func()/i, err.message) - end - - def test_returns - se = Expectation.new(:mock => @mock, :method => 'some_func', - :arguments => [1,'two',:three]) - - se.returns "A value" - - assert_equal "A value", se.apply_method_call(@mock, 'some_func', [1,'two',:three], nil) - end - - def test_apply_method_call_captures_block_value - the_proc = lambda { "in the block" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_nil se.block_value, "Block value starts out nil" - - se.apply_method_call(@mock, 'do_it', [], nil) - - assert_equal "in the block", se.block_value, "Block value not captured" - end - - def test_trigger - # convenience method for block_value.call - target = false - inner_proc = lambda { target = true } - the_proc = lambda { inner_proc } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_nil se.block_value, "Block value starts out nil" - se.apply_method_call(@mock, 'do_it', [], nil) - assert_not_nil se.block_value, "Block value not set" - - assert !target, "Target should still be false" - se.trigger - assert target, "Target not true!" - end - - def test_trigger_with_arguments - # convenience method for block_value.call - target = nil - inner_proc = lambda { |one,two| target = [one,two] } - the_proc = lambda { inner_proc } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_nil se.block_value, "Block value starts out nil" - se.apply_method_call(@mock, 'do_it', [], nil) - assert_not_nil se.block_value, "Block value not set" - - assert_nil target, "target should still be nil" - se.trigger 'cat','dog' - assert_equal ['cat','dog'], target - end - - def test_trigger_nil_block_value - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => []) - - assert_nil se.block_value, "Block value starts out nil" - se.apply_method_call(@mock, 'do_it', [], nil) - assert_nil se.block_value, "Block value should still be nil" - - err = assert_raise ExpectationError do - se.trigger - end - assert_match(/do_it/i, err.message) - assert_match(/block value/i, err.message) - end - - def test_trigger_non_proc_block_value - the_block = lambda { "woops" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_block) - - se.apply_method_call(@mock, 'do_it', [], nil) - assert_equal "woops", se.block_value - - err = assert_raise ExpectationError do - se.trigger - end - assert_match(/do_it/i, err.message) - assert_match(/trigger/i, err.message) - assert_match(/woops/i, err.message) - end - - - - def test_proc_used_for_return - the_proc = lambda { "in the block" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_equal "in the block", se.apply_method_call(@mock, 'do_it', [], nil) - assert_equal "in the block", se.block_value, "Captured block value affected wrongly" - end - - def test_explicit_return_overrides_proc_return - the_proc = lambda { "in the block" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - se.returns "the override" - assert_equal "the override", se.apply_method_call(@mock, 'do_it', [], nil) - assert_equal "in the block", se.block_value, "Captured block value affected wrongly" - end - - def test_yields - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields :bean1, :bean2 - - things = [] - a_block = lambda { |thinger| things << thinger } - - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [:bean1,:bean2], things, "Wrong things" - end - - def test_yields_block_takes_no_arguments - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields - - things = [] - a_block = lambda { things << 'OOF' } - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal ['OOF'], things - end - - def test_yields_params_to_block_takes_no_arguments - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields :wont_fit - - things = [] - a_block = lambda { things << 'WUP' } - - err = assert_raise ExpectationError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/wont_fit/i, err.message) - assert_match(/arity -1/i, err.message) - assert_equal [], things, "Wrong things" - end - - def test_yields_with_returns - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] , - :returns => 'the results') - - exp = se.yields :bean1, :bean2 - assert_same se, exp, "'yields' needs to return a reference to the expectation" - things = [] - a_block = lambda { |thinger| things << thinger } - returned = se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [:bean1,:bean2], things, "Wrong things" - assert_equal 'the results', returned, "Wrong return value" - end - - def test_yields_with_raises - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot], - :raises => RuntimeError.new("kerboom")) - - exp = se.yields :bean1, :bean2 - assert_same se, exp, "'yields' needs to return a reference to the expectation" - things = [] - a_block = lambda { |thinger| things << thinger } - err = assert_raise RuntimeError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/kerboom/i, err.message) - assert_equal [:bean1,:bean2], things, "Wrong things" - end - - def test_yields_and_inner_block_explodes - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot]) - - exp = se.yields :bean1, :bean2 - assert_same se, exp, "'yields' needs to return a reference to the expectation" - things = [] - a_block = lambda { |thinger| - things << thinger - raise "nasty" - } - err = assert_raise RuntimeError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/nasty/i, err.message) - assert_equal [:bean1], things, "Wrong things" - end - - def test_yields_with_several_arrays - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields ['a','b'], ['c','d'] - - things = [] - a_block = lambda { |thinger| things << thinger } - - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [ ['a','b'], ['c','d'] ], things, "Wrong things" - end - - def test_yields_tuples - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields ['a','b','c'], ['d','e','f'] - - things = [] - a_block = lambda { |left,mid,right| - things << { :left => left, :mid => mid, :right => right } - } - - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [ - {:left => 'a', :mid => 'b', :right => 'c' }, - {:left => 'd', :mid => 'e', :right => 'f' }, - ], things, "Wrong things" - end - - def test_yields_tuples_size_mismatch - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields ['a','b','c'], ['d','e','f'] - - things = [] - a_block = lambda { |left,mid| - things << { :left => left, :mid => mid } - } - - err = assert_raise ExpectationError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/arity/i, err.message) - assert_match(/the_mock.each_bean/i, err.message) - assert_match(/"a", "b", "c"/i, err.message) - assert_equal [], things, "Wrong things" - end - - def test_yields_bad_block_arity - se = Expectation.new(:mock => @mock, :method => 'do_later', :arguments => [] ) - se.yields - - assert_error Hardmock::ExpectationError, /block/i, /expected/i, /no param/i, /got 2/i do - se.apply_method_call(@mock,'do_later',[],lambda { |doesnt,match| raise "Surprise!" } ) - end - end - - def test_that_arguments_can_be_added_to_expectation - expectation = Expectation.new(:mock => @mock, :method => "each_bean") - assert_same expectation, expectation.with("jello", "for", "cosby"), "should have returned the same expectation" - - err = assert_raise ExpectationError do - expectation.apply_method_call(@mock, 'each_bean', [], nil) - end - assert_match(/wrong arguments/i, err.message) - - assert_nothing_raised(ExpectationError) do - expectation.apply_method_call(@mock, 'each_bean', ["jello", "for", "cosby"], nil) - end - end - -end diff --git a/vendor/hardmock/test/unit/expector_test.rb b/vendor/hardmock/test/unit/expector_test.rb deleted file mode 100644 index b6cf44be..00000000 --- a/vendor/hardmock/test/unit/expector_test.rb +++ /dev/null @@ -1,65 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/expector' - -class ExpectorTest < Test::Unit::TestCase - include Hardmock - - class MyControl - attr_reader :added - def add_expectation(expectation) - @added ||= [] - @added << expectation - end - end - - class ExpBuilder - attr_reader :options - def build_expectation(options) - @options = options - "dummy expectation" - end - end - - def try_it_with(method_name) - mock = Object.new - mock_control = MyControl.new - builder = ExpBuilder.new - - exp = Expector.new(mock, mock_control, builder) - output = exp.send(method_name,:with, 1, 'sauce') - - assert_same mock, builder.options[:mock] - assert_equal method_name, builder.options[:method].to_s - assert_equal [:with,1,'sauce'], builder.options[:arguments] - assert_nil builder.options[:block] - assert_equal [ "dummy expectation" ], mock_control.added, - "Wrong expectation added to control" - - assert_equal "dummy expectation", output, "Expectation should have been returned" - end - - # - # TESTS - # - def test_method_missing - try_it_with 'wonder_bread' - try_it_with 'whatever' - end - - def test_methods_that_wont_trigger_method_missing - mock = Object.new - mock_control = MyControl.new - builder = ExpBuilder.new - - exp = Expector.new(mock, mock_control, builder) - assert_equal mock, exp.instance_eval("@mock") - end -end diff --git a/vendor/hardmock/test/unit/method_cleanout_test.rb b/vendor/hardmock/test/unit/method_cleanout_test.rb deleted file mode 100644 index 780f92d8..00000000 --- a/vendor/hardmock/test/unit/method_cleanout_test.rb +++ /dev/null @@ -1,44 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' - -class MethodCleanoutTest < Test::Unit::TestCase - class Victim - OriginalMethods = instance_methods - include Hardmock::MethodCleanout - end - - def setup - @victim = Victim.new - end - - def test_should_remove_most_methods_from_a_class - expect_removed = Victim::OriginalMethods.reject { |m| - Hardmock::MethodCleanout::SACRED_METHODS.include?(m) - } - expect_removed.each do |m| - assert !@victim.respond_to?(m), "should not have method #{m}" - end - end - - def test_should_leave_the_sacred_methods_defined - Hardmock::MethodCleanout::SACRED_METHODS.each do |m| - next if m =~ /^hm_/ - assert @victim.respond_to?(m), "Sacred method '#{m}' was removed unexpectedly" - end - end - - def test_should_include_certain_important_methods_in_the_sacred_methods_list - %w|__id__ __send__ equal? object_id send nil? class kind_of? respond_to? inspect method to_s instance_variables instance_eval|.each do |m| - assert Hardmock::MethodCleanout::SACRED_METHODS.include?(m), "important method #{m} is not included in SACRED_METHODS" - end - end - -end diff --git a/vendor/hardmock/test/unit/mock_control_test.rb b/vendor/hardmock/test/unit/mock_control_test.rb deleted file mode 100644 index 131c9d48..00000000 --- a/vendor/hardmock/test/unit/mock_control_test.rb +++ /dev/null @@ -1,183 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/utils' -require 'hardmock/errors' -require 'hardmock/mock_control' - -class MockControlTest < Test::Unit::TestCase - include Hardmock - - def setup - @unmock = OpenStruct.new( :_name => 'fakemock' ) - - @control = MockControl.new - assert @control.happy?, "Control should start out happy" - end - - def teardown - end - - # - # HELPERS - # - - class MyExp - attr_reader :mock, :mname, :args, :block - def apply_method_call(mock, mname, args, block) - @mock = mock - @mname = mname - @args = args - @block = block - end - end - - class BoomExp < MyExp - def apply_method_call(mock, mname, args, block) - super - raise "BOOM" - end - end - - # - # TESTS - # - - def test_add_exepectation_and_apply_method_call - e1 = MyExp.new - - @control.add_expectation e1 - assert !@control.happy? - - @control.apply_method_call @unmock, 'some_func', [ 'the', :args ], nil - assert @control.happy? - - assert_same @unmock, e1.mock, "Wrong mock" - assert_equal 'some_func', e1.mname, "Wrong method" - assert_equal [ 'the', :args ], e1.args, "Wrong args" - - @control.verify - end - - def test_add_exepectation_and_apply_method_call_with_block - e1 = MyExp.new - - @control.add_expectation e1 - assert !@control.happy? - - runtime_block = Proc.new { "hello" } - @control.apply_method_call @unmock, 'some_func', [ 'the', :args ], runtime_block - assert @control.happy? - - assert_same @unmock, e1.mock, "Wrong mock" - assert_equal 'some_func', e1.mname, "Wrong method" - assert_equal [ 'the', :args ], e1.args, "Wrong args" - assert_equal "hello", e1.block.call, "Wrong block in expectation" - - @control.verify - end - - def test_add_expectation_then_verify - e1 = MyExp.new - - @control.add_expectation e1 - assert !@control.happy?, "Shoudn't be happy" - err = assert_raise VerifyError do - @control.verify - end - assert_match(/unmet expectations/i, err.message) - - @control.apply_method_call @unmock, 'some_func', [ 'the', :args ], nil - assert @control.happy? - - assert_same @unmock, e1.mock, "Wrong mock" - assert_equal 'some_func', e1.mname, "Wrong method" - assert_equal [ 'the', :args ], e1.args, "Wrong args" - - @control.verify - end - - def test_expectation_explosion - be1 = BoomExp.new - - @control.add_expectation be1 - - err = assert_raise RuntimeError do - @control.apply_method_call @unmock, 'a func', [:arg], nil - end - assert_match(/BOOM/i, err.message) - - assert_same @unmock, be1.mock - assert_equal 'a func', be1.mname - assert_equal [:arg], be1.args - end - - def test_disappointment_on_bad_verify - @control.add_expectation MyExp.new - assert !@control.happy?, "Shouldn't be happy" - assert !@control.disappointed?, "too early to be disappointed" - - # See verify fails - err = assert_raise VerifyError do - @control.verify - end - assert_match(/unmet expectations/i, err.message) - - assert !@control.happy?, "Still have unmet expectation" - assert @control.disappointed?, "We should be disappointed following that failure" - - @control.apply_method_call @unmock, 'something', [], nil - assert @control.happy?, "Should be happy" - assert @control.disappointed?, "We should be skeptical" - - @control.verify - - assert !@control.disappointed?, "Should be non-disappointed" - end - - def test_disappointment_from_surprise_calls - assert @control.happy?, "Should be happy" - assert !@control.disappointed?, "too early to be disappointed" - - # See verify fails - err = assert_raise ExpectationError do - @control.apply_method_call @unmock, "something", [], nil - end - assert_match(/surprise/i, err.message) - - assert @control.happy?, "Happiness is an empty list of expectations" - assert @control.disappointed?, "We should be disappointed following that failure" - - @control.verify - assert !@control.disappointed?, "Disappointment should be gone" - end - - def test_disappointment_from_bad_calls - be1 = BoomExp.new - assert !@control.disappointed?, "Shouldn't be disappointed" - @control.add_expectation be1 - assert !@control.disappointed?, "Shouldn't be disappointed" - - err = assert_raise RuntimeError do - @control.apply_method_call @unmock, 'a func', [:arg], nil - end - assert_match(/BOOM/i, err.message) - assert @control.disappointed?, "Should be disappointed" - - assert_same @unmock, be1.mock - assert_equal 'a func', be1.mname - assert_equal [:arg], be1.args - - assert @control.happy?, "Happiness is an empty list of expectations" - @control.verify - assert !@control.disappointed?, "Disappointment should be gone" - end - - -end diff --git a/vendor/hardmock/test/unit/mock_test.rb b/vendor/hardmock/test/unit/mock_test.rb deleted file mode 100644 index 5a61f84d..00000000 --- a/vendor/hardmock/test/unit/mock_test.rb +++ /dev/null @@ -1,287 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' -require 'hardmock/mock' -require 'hardmock/mock_control' -require 'hardmock/expectation_builder' -require 'hardmock/expector' -require 'hardmock/trapper' - -class MockTest < Test::Unit::TestCase - include Hardmock - - def test_build_with_control - mc1 = MockControl.new - mock = Mock.new('hi', mc1) - assert_equal 'hi', mock._name, "Wrong name" - assert_same mc1, mock._control, "Wrong contol" - end - - def test_basics - mock = Mock.new('a name') - assert_equal 'a name', mock._name, "Wrong name for mock" - assert_not_nil mock._control, "Nil control in mock" - end - - def test_expects - mock = Mock.new('order') - control = mock._control - assert control.happy?, "Mock should start out satisfied" - - mock.expects.absorb_something(:location, 'garbage') - assert !control.happy?, "mock control should be unhappy" - - # Do the call - mock.absorb_something(:location, 'garbage') - assert control.happy?, "mock control should be happy again" - - # Verify - assert_nothing_raised Exception do - mock._verify - end - end - - def test_expects_using_arguments_for_method_and_arguments - mock = Mock.new('order') - mock.expects(:absorb_something, :location, 'garbage') - mock.absorb_something(:location, 'garbage') - mock._verify - end - - def test_expects_using_arguments_for_method_and_arguments_with_block - mock = Mock.new('order') - mock.expects(:absorb_something, :location, 'garbage') { |a,b,block| - assert_equal :location, a, "Wrong 'a' argument" - assert_equal 'garbage', b, "Wrong 'b' argument" - assert_equal 'innards', block.call, "Wrong block" - } - mock.absorb_something(:location, 'garbage') do "innards" end - mock._verify - end - - def test_expects_using_string_method_name - mock = Mock.new('order') - mock.expects('absorb_something', :location, 'garbage') - mock.absorb_something(:location, 'garbage') - mock._verify - end - - - def test_expects_assignment - mock = Mock.new('order') - mock.expects.account_number = 1234 - - mock.account_number = 1234 - - mock._verify - end - - def test_expects_assigment_using_arguments_for_method_and_arguments - mock = Mock.new('order') - mock.expects(:account_number=, 1234) - mock.account_number = 1234 - mock._verify - end - - def test_expects_assigment_using_string_method_name - mock = Mock.new('order') - mock.expects('account_number=', 1234) - mock.account_number = 1234 - mock._verify - end - - def test_expects_assignment_and_return_is_overruled_by_ruby_syntax - # Prove that we can set up a return but that it doesn't mean much, - # because ruby's parser will 'do the right thing' as regards semantic - # values for assignment. (That is, the rvalue of the assignment) - mock = Mock.new('order') - mock.expects(:account_number=, 1234).returns "gold" - got = mock.account_number = 1234 - mock._verify - assert_equal 1234, got, "Expected rvalue" - end - - def test_expects_assignment_and_raise - mock = Mock.new('order') - mock.expects(:account_number=, 1234).raises StandardError.new("kaboom") - err = assert_raise StandardError do - mock.account_number = 1234 - end - assert_match(/kaboom/i, err.message) - mock._verify - end - - - def test_expects_multiple - mock = Mock.new('order') - control = mock._control - - assert control.happy? - - mock.expects.one_thing :hi, { :goose => 'neck' } - mock.expects.another 5,6,7 - assert !control.happy? - - mock.one_thing :hi, { :goose => 'neck' } - assert !control.happy? - - mock.another 5,6,7 - assert control.happy? - end - - def test_surprise_call - mock = Mock.new('order') - err = assert_raise ExpectationError do - mock.uh_oh - end - assert_match(/surprise/i, err.message) - assert_match(/uh_oh/i, err.message) - - err = assert_raise ExpectationError do - mock.whoa :horse - end - assert_match(/surprise/i, err.message) - assert_match(/order\.whoa\(:horse\)/i, err.message) - end - - def test_wrong_call - mock = Mock.new('order') - mock.expects.pig 'arse' - err = assert_raise ExpectationError do - mock.whoa :horse - end - assert_match(/wrong method/i, err.message) - assert_match(/order\.whoa\(:horse\)/i, err.message) - assert_match(/order\.pig\("arse"\)/i, err.message) - end - - def test_wrong_arguments - mock = Mock.new('order') - mock.expects.go_fast(:a, 1, 'three') - - err = assert_raise ExpectationError do - mock.go_fast :a, 1, 'not right' - end - assert_match(/wrong argument/i, err.message) - assert_match(/order\.go_fast\(:a, 1, "three"\)/i, err.message) - assert_match(/order\.go_fast\(:a, 1, "not right"\)/i, err.message) - end - - def test_expects_and_return - mock = Mock.new('order') - mock.expects.delivery_date.returns Date.today - assert_equal Date.today, mock.delivery_date - mock._verify - end - - def test_expects_and_return_with_arguments - mock = Mock.new('order') - mock.expects.delivery_date(:arf,14).returns(Date.today) - assert_equal Date.today, mock.delivery_date(:arf,14) - mock._verify - end - - def test_expects_and_raise - mock = Mock.new('order') - mock.expects.delivery_date.raises StandardError.new("bloof") - - err = assert_raise StandardError do - mock.delivery_date - end - assert_match(/bloof/i, err.message) - - mock._verify - - # Try convenience argument String - mock.expects.pow.raises "hell" - err = assert_raise RuntimeError do - mock.pow - end - assert_match(/hell/i, err.message) - - mock._verify - - # Try convenience argument nothing - mock.expects.pow.raises - err = assert_raise RuntimeError do - mock.pow - end - assert_match(/an error/i, err.message) - - mock._verify - end - - def test_expects_a_runtime_block - mock = Mock.new('order') - got_val = nil - - mock.expects.when(:something) { |e,block| - got_val = block.call - } - - mock.when :something do "hi there" end - - assert_equal "hi there", got_val, "Expectation block not invoked" - mock._verify - end - - def test_trap_block - mock = Mock.new('order') - exp = mock.trap.observe - - # use it - mock.observe { "burp" } - - assert_equal "burp", exp.block_value.call - end - - def test_trap_arguments_and_block - mock = Mock.new('order') - exp = mock.trap.subscribe(:data_changed) - - # use it - mock.subscribe(:data_changed) { "burp" } - assert_equal "burp", exp.block_value.call - mock._verify - end - - def test_trap_arguments_and_block_wrong_num_args - mock = Mock.new('order') - exp = mock.trap.subscribe(:data_changed) - - assert_raise ExpectationError do - mock.subscribe(:data_changed,1) { "burp" } - end - mock._verify - end - - def test_trap_arguments_and_block_wrong_args - mock = Mock.new('order') - exp = mock.trap.subscribe(:data_changed) - - assert_raise ExpectationError do - mock.subscribe("no good") { "burp" } - end - - mock._verify - end - - def test_trap_is_not_leniant_about_arguments - mock = Mock.new('order') - exp = mock.trap.subscribe - - assert_raise ExpectationError do - mock.subscribe("no good") { "burp" } - end - - mock._verify - end - -end diff --git a/vendor/hardmock/test/unit/test_unit_before_after_test.rb b/vendor/hardmock/test/unit/test_unit_before_after_test.rb deleted file mode 100644 index c12ffb28..00000000 --- a/vendor/hardmock/test/unit/test_unit_before_after_test.rb +++ /dev/null @@ -1,460 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") - -class TestUnitBeforeAfter < Test::Unit::TestCase - - # - # after_teardown - # - - it "adds TestCase.after_teardown hook for appending post-teardown actions" do - write_and_run_test :use_after_teardown => true - - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "2nd after_teardown", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - should "execute all after_teardowns, even if the main teardown flunks" do - write_and_run_test :use_after_teardown => true, :flunk_in_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "F", - "1st after_teardown", - "2nd after_teardown", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:20]:", - "FLUNK IN TEARDOWN" - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - should "execute all after_teardowns, even if the main teardown explodes" do - write_and_run_test :use_after_teardown => true, :raise_in_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "E", - "1st after_teardown", - "2nd after_teardown", - "Finished in", - "RuntimeError: ERROR IN TEARDOWN" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 1 - end - - should "execute all after_teardowns, even if some of them flunk" do - write_and_run_test :use_after_teardown => true, :flunk_in_after_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "F", - "2nd after_teardown", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:7]:", - "Flunk in first after_teardown", - "2) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:10]:", - "Flunk in second after_teardown" - see_results :tests => 1, :assertions => 2, :failures => 2, :errors => 0 - end - - should "execute all after_teardowns, even if some of them explode" do - write_and_run_test :use_after_teardown => true, :raise_in_after_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "E", - "2nd after_teardown", - "Finished in", - "RuntimeError: Error in first after_teardown", - "RuntimeError: Error in second after_teardown" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 2 - end - - it "will run after_teardowns in the absence of a regular teardown" do - write_and_run_test :omit_teardown => true, :use_after_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "1st after_teardown", - "2nd after_teardown", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - should "not interfere with normal test writing" do - write_and_run_test - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - it "provides a cleaned-up backtrace" do - write_and_run_test :with_failure => true - see_in_order "Loaded suite", - "THE SETUP", - "A FAILING TEST", - "F", "THE TEARDOWN", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:17]:", - "Instrumented failure.", - " is not true." - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - it "provides a cleaned-up backtrace, but not TOO cleaned up" do - write_and_run_test :with_failure => true, :use_helpers => true - see_in_order "Loaded suite", - "THE SETUP", - "A FAILING TEST", - "F", "THE TEARDOWN", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest)\n", - "[_test_file_temp.rb:25:in `tripwire'", - "_test_file_temp.rb:21:in `my_helper'", - "_test_file_temp.rb:17:in `test_something']:", - "Instrumented failure.", - " is not true." - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - should "not interfere with passthrough exception types" do - if is_modern_test_unit? - write_and_run_test :raise_nasty_in_test => true - see_in_no_particular_order "Loaded suite", - "THE TEARDOWN", - "_test_file_temp.rb:16:in `test_something': NASTY ERROR (NoMemoryError)" - see_no_results - end - end - - # - # before_setup - # - - it "adds TestCase.before_setup hook for prepending pre-setup actions" do - write_and_run_test :use_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "1st before_setup", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - should "stop executing the test on the first failure withing a before_setup action" do - write_and_run_test :use_before_setup => true, :flunk_in_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "FTHE TEARDOWN", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:10]:", - "Flunk in 2nd before_setup." - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - should "stop executing the test on the first error within a before_setup action" do - write_and_run_test :use_before_setup => true, :raise_in_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "ETHE TEARDOWN", - "Finished in", - "test_something(MyExampleTest):", - "RuntimeError: Error in 2nd before_setup", - "_test_file_temp.rb:10", - "/hardmock/lib/test_unit_before_after.rb:", ":in `call'" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 1 - end - - it "will run before_setup actions in the absence of a regular setup" do - write_and_run_test :omit_setup => true, :use_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "1st before_setup", - "A TEST", - "THE TEARDOWN", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - it "allows before_setup and after_teardown to be used at the same time" do - write_and_run_test :use_before_setup => true, :use_after_teardown => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "1st before_setup", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "2nd after_teardown", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - # - # HELPERS - # - - def teardown - remove_test - end - - def test_filename - "_test_file_temp.rb" - end - - def remove_test - rm_f test_filename - end - - def write_and_run_test(opts={}) - write(test_filename, generate_test_code(opts)) - run_test - end - - def run_test - @output = `ruby #{test_filename} 2>&1` - end - - - def write(fname, code) - File.open(fname,"w") do |f| - f.print code - end - end - - def show_output - puts "-- BEGIN TEST OUTPUT" - puts @output - puts "-- END TEST OUTPUT" - end - - def see_in_order(*phrases) - idx = 0 - phrases.each do |txt| - idx = @output.index(txt, idx) - if idx.nil? - if @output.index(txt) - flunk "Phrase '#{txt}' is out-of-order in test output:\n#{@output}" - else - flunk "Phrase '#{txt}' not found in test output:\n#{@output}" - end - end - end - end - - def see_in_no_particular_order(*phrases) - phrases.each do |txt| - assert_not_nil @output.index(txt), "Didn't see '#{txt}' in test output:\n#{@output}" - end - end - - def see_results(opts) - if @output =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ - tests, assertions, failures, errors = [ $1, $2, $3, $4 ] - [:tests, :assertions, :failures, :errors].each do |key| - eval %{assert_equal(opts[:#{key}].to_s, #{key}, "Wrong number of #{key} in report") if opts[:#{key}]} - end - else - flunk "Didn't see the test results report line" - end - end - - def see_no_results - if @output =~ /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/ - flunk "Should not have had a results line:\n#{@output}" - end - end - - def lib_dir - File.expand_path(File.dirname(__FILE__) + "/../../lib") - end - - def generate_test_code(opts={}) - - if opts[:with_failure] or opts[:raise_nasty_in_test] - test_method_code = generate_failing_test("test_something", opts) - else - test_method_code = generate_passing_test("test_something") - end - - - requires_for_ext = '' - if opts[:use_before_setup] or opts[:use_after_teardown] - requires_for_ext =<<-RFE - $: << "#{lib_dir}" - require 'test_unit_before_after' - RFE - end - - before_setups = '' - if opts[:use_before_setup] - add_on_two = "" - if opts[:flunk_in_before_setup] - add_on_two = %{; test.flunk "Flunk in 2nd before_setup"} - elsif opts[:raise_in_before_setup] - add_on_two = %{; raise "Error in 2nd before_setup"} - end - before_setups =<<-BSTS - Test::Unit::TestCase.before_setup do |test| - puts "1st before_setup" - end - Test::Unit::TestCase.before_setup do |test| - puts "2nd before_setup" #{add_on_two} - end - Test::Unit::TestCase.before_setup do |test| - puts "3rd before_setup" - end - - BSTS - end - - - setup_code =<<-SC - def setup - puts "THE SETUP" - end - SC - if opts[:omit_setup] - setup_code = "" - end - - after_teardowns = '' - if opts[:use_after_teardown] - add_on_one = "" - add_on_two = "" - if opts[:flunk_in_after_teardown] - add_on_one = %{; test.flunk "Flunk in first after_teardown"} - add_on_two = %{; test.flunk "Flunk in second after_teardown"} - elsif opts[:raise_in_after_teardown] - add_on_one = %{; raise "Error in first after_teardown"} - add_on_two = %{; raise "Error in second after_teardown"} - end - after_teardowns =<<-ATDS - Test::Unit::TestCase.after_teardown do |test| - puts "1st after_teardown" #{add_on_one} - end - Test::Unit::TestCase.after_teardown do |test| - puts "2nd after_teardown" #{add_on_two} - end - ATDS - end - - teardown_code =<<-TDC - def teardown - puts "THE TEARDOWN" - end - TDC - if opts[:flunk_in_teardown] - teardown_code =<<-TDC - def teardown - flunk "FLUNK IN TEARDOWN" - end - TDC - elsif opts[:raise_in_teardown] - teardown_code =<<-TDC - def teardown - raise "ERROR IN TEARDOWN" - end - TDC - end - if opts[:omit_teardown] - teardown_code = "" - end - - str = <<-TCODE - require 'test/unit' - #{requires_for_ext} - - #{before_setups} #{after_teardowns} - - class MyExampleTest < Test::Unit::TestCase - #{setup_code} - #{teardown_code} - #{test_method_code} - end - TCODE - end - - def generate_passing_test(tname) - str = <<-TMETH - def #{tname} - puts "A TEST" - end - TMETH - end - - def generate_failing_test(tname, opts={}) - str = "NOT DEFINED?" - if opts[:raise_nasty_in_test] - str = <<-TMETH - def #{tname} - raise NoMemoryError, "NASTY ERROR" - end - TMETH - - elsif opts[:use_helpers] - str = <<-TMETH - def #{tname} - puts "A FAILING TEST" - my_helper - end - - def my_helper - tripwire - end - - def tripwire - assert false, "Instrumented failure" - end - TMETH - else - str = <<-TMETH - def #{tname} - puts "A FAILING TEST" - assert false, "Instrumented failure" - end - TMETH - end - return str - end - - def is_modern_test_unit? - begin - Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS - return true - rescue NameError - return false - end - end - -end diff --git a/vendor/hardmock/test/unit/trapper_test.rb b/vendor/hardmock/test/unit/trapper_test.rb deleted file mode 100644 index 36ca413d..00000000 --- a/vendor/hardmock/test/unit/trapper_test.rb +++ /dev/null @@ -1,70 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' -require 'hardmock/trapper' - -class TrapperTest < Test::Unit::TestCase - include Hardmock - - def setup - @mock = Object.new - @mock_control = MyControl.new - @builder = ExpBuilder.new - @trapper = Trapper.new(@mock, @mock_control, @builder) - end - - # - # HELPERS - # - - class MyControl - attr_reader :added - def add_expectation(expectation) - @added ||= [] - @added << expectation - end - end - - class ExpBuilder - attr_reader :options - def build_expectation(options) - @options = options - "dummy expectation" - end - end - - # - # TESTS - # - - def test_method_missing - - output = @trapper.change(:less) - - assert_same @mock, @builder.options[:mock] - assert_equal :change, @builder.options[:method] - assert_equal [:less], @builder.options[:arguments] - assert_not_nil @builder.options[:block] - assert @builder.options[:suppress_arguments_to_block], ":suppress_arguments_to_block should be set" - assert_equal [ "dummy expectation" ], @mock_control.added, - "Wrong expectation added to control" - - assert_equal "dummy expectation", output, "Expectation should have been returned" - - # Examine the block. It should take one argument and simply return - # that argument. because of the 'suppress arguments to block' - # setting, the argument can only end up being a block, in practice. - trapper_block = @builder.options[:block] - assert_equal "the argument", trapper_block.call("the argument"), - "The block should merely return the passed argument" - end - - -end diff --git a/vendor/hardmock/test/unit/verify_error_test.rb b/vendor/hardmock/test/unit/verify_error_test.rb deleted file mode 100644 index 2dca2b48..00000000 --- a/vendor/hardmock/test/unit/verify_error_test.rb +++ /dev/null @@ -1,48 +0,0 @@ -# ========================================================================= -# Ceedling - Test-Centered Build System for C -# ThrowTheSwitch.org -# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams -# SPDX-License-Identifier: MIT -# ========================================================================= - - -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' -require 'hardmock/mock_control' -require 'hardmock/errors' -require 'hardmock/expectation_builder' -require 'hardmock/expectation' -require 'hardmock/mock' - -class VerifyErrorTest < Test::Unit::TestCase - include Hardmock - - # - # TESTS - # - - def test_formatted_list_of_unmet_expectations - mock1 = Mock.new('mock1') - mock2 = Mock.new('mock2') - exp1 = Expectation.new( :mock => mock1, :method => 'send_parts', :arguments => [1,2,:a] ) - exp2 = Expectation.new( :mock => mock2, :method => 'grind_it', :arguments => [] ) - - exp_list = [ exp1, exp2 ] - - err = VerifyError.new("This is the error", exp_list) - assert_equal "This is the error:\n * #{exp1.to_s}\n * #{exp2.to_s}", err.message - end - - def test_empty_list_of_expectations - # this is not a normal case; not spending a lot of time to make this better - exp_list = [] - err = VerifyError.new("This is the error:\n", exp_list) - end - - def test_nil_expectation_list - # this is not a normal case; not spending a lot of time to make this better - exp_list = [] - err = VerifyError.new("This is the error:\n", exp_list) - end - -end From 6422aca41686098b75233af7c22198f8a1ad965c Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 16:06:57 -0500 Subject: [PATCH 32/35] =?UTF-8?q?=F0=9F=92=9A=20=20Updated=20/=20added=20G?= =?UTF-8?q?em=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shenanigans around unspecified versions in Gemfile and old versions in .gemspec were causing Github Action build failures --- Gemfile | 14 +++++++------- Gemfile.lock | 28 +++++++++++++++------------- ceedling.gemspec | 3 ++- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 887f9485..a9a7bbc6 100644 --- a/Gemfile +++ b/Gemfile @@ -8,15 +8,15 @@ source "http://rubygems.org/" gem "bundler", "~> 2.5" -gem "rake" gem "rspec", "~> 3.8" -gem "require_all" -gem "constructor" -gem "diy" +gem "rake", ">= 12", "< 14" gem "rr" -gem "thor" -gem "deep_merge" -gem "unicode-display_width" +gem "require_all" +gem "diy", "~> 1.1" +gem "constructor", "~> 2" +gem "thor", "~> 1.3" +gem "deep_merge", "~> 1.2" +gem "unicode-display_width", "~> 3.1" #these will be used if present, but ignored otherwise #gem "curses" diff --git a/Gemfile.lock b/Gemfile.lock index 5cb2894c..8133ca0c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,22 +8,24 @@ GEM constructor (>= 1.0.0) rake (13.2.1) require_all (3.0.0) - rr (3.1.0) + rr (3.1.1) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.2) rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) - thor (1.3.1) - unicode-display_width (2.5.0) + thor (1.3.2) + unicode-display_width (3.1.2) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) PLATFORMS ruby @@ -34,15 +36,15 @@ PLATFORMS DEPENDENCIES bundler (~> 2.5) - constructor - deep_merge - diy - rake + constructor (~> 2) + deep_merge (~> 1.2) + diy (~> 1.1) + rake (>= 12, < 14) require_all rr rspec (~> 3.8) - thor - unicode-display_width + thor (~> 1.3) + unicode-display_width (~> 3.1) BUNDLED WITH - 2.5.10 + 2.5.23 diff --git a/ceedling.gemspec b/ceedling.gemspec index 3b4bc22f..d29342e9 100644 --- a/ceedling.gemspec +++ b/ceedling.gemspec @@ -42,8 +42,9 @@ Ceedling projects are created with a YAML configuration file. A variety of conve s.add_dependency "thor", "~> 1.3" s.add_dependency "rake", ">= 12", "< 14" s.add_dependency "deep_merge", "~> 1.2" + s.add_dependency "diy", "~> 1.1" s.add_dependency "constructor", "~> 2" - s.add_dependency "unicode-display_width", "~> 2.5" + s.add_dependency "unicode-display_width", "~> 3.1" # Files needed from submodules s.files = [] From 7b2b2442aa7eae5c9e11c01b97b76387da6564b1 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 16:07:52 -0500 Subject: [PATCH 33/35] =?UTF-8?q?=F0=9F=92=9A=20Better=20Action=20task=20n?= =?UTF-8?q?ames=20plus=20tiny=20gem=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index baa12598..5179f8cc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,15 +51,14 @@ jobs: submodules: recursive # Setup Ruby Testing Tools to do tests on multiple ruby version - - name: Setup Ruby Testing Tools + - name: Setup Ruby Version Matrix uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - # Install Ruby Testing Tools (Bundler version should match the one in Gemfile.lock) - - name: Install Ruby Testing Tools + # Install Gem Depdencies (Bundler version should match the one in Gemfile.lock) + - name: Install Gem Dependencies for Testing and Ceedling Gem Builds run: | - gem install rspec gem install rubocop -v 0.57.2 gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" bundle update @@ -90,8 +89,8 @@ jobs: run: | rake ci - # Build & Install Gem - - name: Build and Install Gem + # Build & Install Ceedling Gem + - name: Build and Install Ceedling Gem run: | gem build ceedling.gemspec gem install --local ceedling-*.gem From efe4a1ef2a98244aae31b003ca9451d449c02227 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 16:22:53 -0500 Subject: [PATCH 34/35] =?UTF-8?q?=F0=9F=92=9A=20Better=20Action=20task=20n?= =?UTF-8?q?ames=20+=20Windows=20tweak?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5179f8cc..894cd1c2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -50,7 +50,7 @@ jobs: with: submodules: recursive - # Setup Ruby Testing Tools to do tests on multiple ruby version + # Setup Ruby to run test & build steps on multiple ruby versions - name: Setup Ruby Version Matrix uses: ruby/setup-ruby@v1 with: @@ -103,21 +103,21 @@ jobs: cd ../.. # Run FFF Plugin Tests - - name: Run Tests on FFF Plugin + - name: "Run Tests on Ceedling Plugin: FFF" run: | cd plugins/fff rake cd ../.. # Run Module Generator Plugin Tests - - name: Run Tests on Module Generator Plugin + - name: "Run Tests on Ceedling Plugin: Module Generator" run: | cd plugins/module_generator rake cd ../.. # Run Dependencies Plugin Tests - - name: Run Tests on Dependencies Plugin + - name: "Run Tests on Ceedling Plugin: Dependencies" run: | cd plugins/dependencies rake @@ -146,30 +146,28 @@ jobs: with: submodules: recursive - # Setup Ruby Testing Tools to do tests on multiple ruby version - - name: Set Up Ruby Testing Tools + # Setup Ruby to run test & build steps on multiple ruby versions + - name: Setup Ruby Version Matrix uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - # Install Ruby Testing Tools - # Bundler version should match the one in Gemfile.lock - - name: Install Ruby Testing Tools + # Install Gem Depdencies (Bundler version should match the one in Gemfile.lock) + - name: Install Gem Dependencies for Testing and Ceedling Gem Builds shell: bash run: | - gem install rspec gem install rubocop -v 0.57.2 gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" bundle update bundle install # Install GCovr for Gcov plugin test - - name: Install GCovr for Gcov Plugin Tests + - name: "Install GCovr for Tests of Ceedling Plugin: Gcov" run: | pip install gcovr # Install ReportGenerator for Gcov plugin test - - name: Install ReportGenerator for Gcov Plugin Tests + - name: "Install ReportGenerator for Tests of Ceedling Plugin: Gcov" run: | dotnet tool install --global dotnet-reportgenerator-globaltool @@ -179,7 +177,7 @@ jobs: rake ci # Build & Install Gem - - name: Build and Install Gem + - name: Build and Install Ceedling Gem run: | gem build ceedling.gemspec gem install --local ceedling-*.gem @@ -192,21 +190,21 @@ jobs: cd ../.. # Run FFF Plugin Tests - - name: Run Tests on FFF Plugin + - name: "Run Tests on Ceedling Plugin: FFF" run: | cd plugins/fff rake cd ../.. # Run Module Generator Plugin Tests - - name: Run Tests on Module Generator Plugin + - name: "Run Tests on Ceedling Plugin: Module Generator" run: | cd plugins/module_generator rake cd ../.. # Run Dependencies Plugin Tests - - name: Run Tests on Dependencies Plugin + - name: "Run Tests on Ceedling Plugin: Dependencies" run: | cd plugins/dependencies rake From 5648fe20971685b3d279175e1dd8c23dfb7bf198 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Thu, 28 Nov 2024 16:27:24 -0500 Subject: [PATCH 35/35] =?UTF-8?q?=F0=9F=92=A1=20Added=20Gemfile=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index a9a7bbc6..7a7d1d5c 100644 --- a/Gemfile +++ b/Gemfile @@ -8,10 +8,14 @@ source "http://rubygems.org/" gem "bundler", "~> 2.5" + +# Testing tools gem "rspec", "~> 3.8" gem "rake", ">= 12", "< 14" gem "rr" gem "require_all" + +# Ceedling dependencies gem "diy", "~> 1.1" gem "constructor", "~> 2" gem "thor", "~> 1.3"