lib/assert_same.rb in assert_same-0.3 vs lib/assert_same.rb in assert_same-0.5

- old
+ new

@@ -47,10 +47,19 @@ $assert_same_options << "--autoaccept" if ARGV.include?("--autoaccept") end +#Use this to raise internal error with a given message +#You can define your own method for your application +unless defined? internal_error + def internal_error(message = 'internal error') + raise message + end +end + + module Test::Unit::Assertions #Hash[filename][line_number] = offset #For each line in the original file we store its offset (+N or -N lines) #relative to the actual file @@ -66,10 +75,18 @@ # assert_same something, <<-END # foo # bar # zee # END + # + # You can also use assert_same for blocks. Then you can assert block result or raised exception + # assert_same(<<-END) do + # Exception NoMethodError: undefined method `+' for nil:NilClass + # END + # # Code block starts here + # c = nil + 1 + # end # # Then run tests as usual: # rake test:units # ruby test/unit/foo_test.rb # ... @@ -148,29 +165,43 @@ # correctly updates all assert_same's in the test file # - it's ok to omit expected string, like this: # assert_same something # in fact, this is the preferred way to create assert_same tests - you write empty # assert_same, run tests and they will fill expected values for you automatically - def assert_same(actual, expected = :autofill_expected_value) - if expected.class == String - expected ||= "" - mode = :expecting_string - elsif expected == :autofill_expected_value + def assert_same(*args) + if block_given? + mode = :block + expected = args[0] + actual = "" + begin + actual = yield.to_s + rescue Exception => e + actual = "Exception #{e.class}: #{e.message}" + end + else + mode = :scalar + expected = args[1] + actual = args[0] + end + + if expected.nil? expected = "" - mode = :autofill_expected_value + change = :create_expected_string + elsif expected.class == String + change = :update_expected_string elsif expected.class == Hash raise ":log key is missing" unless expected.has_key? :log - mode = :expecting_file log_file = expected[:log] if defined? RAILS_ROOT log_file = File.expand_path(log_file, RAILS_ROOT) else log_file = File.expand_path(log_file, Dir.pwd) end expected = File.exists?(log_file) ? File.read(log_file) : "" + change = :update_file_with_expected_string else - internal_error("Incorrect expected argument for assert_same. It must be either String or Hash.") + internal_error("Invalid expected class #{excepted.class}") end # interactive mode is turned on by default, except when # - --no-interactive is given # - STDIN is not a terminal device (i.e. we can't ask any questions) @@ -196,14 +227,16 @@ $assert_same_options << "--autoaccept" if response == "Y" $assert_same_options << "--no-interactive" if response == "N" end if accept - if [:expecting_string, :autofill_expected_value].include? mode - accept_string(actual, mode) - elsif mode == :expecting_file + if [:create_expected_string, :update_expected_string].include? change + accept_string(actual, change, mode) + elsif change == :update_file_with_expected_string accept_file(actual, log_file) + else + internal_error("Invalid change #{change}") end end end if accept # when change is accepted, we should not report it as a failure because @@ -244,11 +277,14 @@ else raise MiniTest::Assertion.new(diff) end end - def accept_string(actual, mode) + # actual - actual value of the scalar or result of the executed block + # change - what to do with expected value (:create_expected_string or :update_expected_string) + # mode - describes signature of assert_same call by type of main argument (:block or :scalar) + def accept_string(actual, change, mode) file, method, line = get_caller_location(:depth => 3) # read source file, construct the new source, replacing everything # between "do" and "end" in assert_same's block # using File::expand_path here because "file" can be either @@ -260,34 +296,43 @@ offset = @@file_offsets[file].keys.inject(0) do |sum, i| line.to_i >= i ? sum + @@file_offsets[file][i] : sum end expected_text_end_line = expected_text_start_line = line.to_i + offset - unless mode == :autofill_expected_value - #if we're autofilling the value, END/EOS marker will not exist - #(second arg to assert_same is omitted) - #else we search for it + if change == :update_expected_string + #search for the end of expected value in code expected_text_end_line += 1 while !["END", "EOS"].include?(source[expected_text_end_line].strip) + elsif change == :create_expected_string + # The is no expected value yet. expected_text_end_line is unknown + else + internal_error("Invalid change #{change}") end expected_length = expected_text_end_line - expected_text_start_line # indentation is the indentation of assert_same call + 4 indentation = source[expected_text_start_line-1] =~ /^(\s+)/ ? $1.length : 0 indentation += 4 - if mode == :autofill_expected_value - # add second argument to assert_same if it's omitted - source[expected_text_start_line-1] = "#{source[expected_text_start_line-1].chop}, <<-END\n" + if change == :create_expected_string + if mode == :scalar + # add second argument to assert_same if it's omitted + source[expected_text_start_line-1] = "#{source[expected_text_start_line-1].chop}, <<-END\n" + elsif mode == :block + # add expected value as argument to assert_same before block call + source[expected_text_start_line-1] = source[expected_text_start_line-1].sub(/assert_same(\(.*?\))*/, "assert_same(<<-END)") + else + internal_error("Invalid mode #{mode}") + end end source = source[0, expected_text_start_line] + actual.split("\n").map { |l| "#{" "*(indentation)}#{l}\n"} + - (mode == :autofill_expected_value ? ["#{" "*(indentation-4)}END\n"] : [])+ + (change == :create_expected_string ? ["#{" "*(indentation-4)}END\n"] : [])+ source[expected_text_end_line, source.length] # recalculate line number adjustments actual_length = actual.split("\n").length - actual_length += 1 if mode == :autofill_expected_value # END marker after actual value + actual_length += 1 if change == :create_expected_string # END marker after expected value @@file_offsets[file][line.to_i] = actual_length - expected_length source_file = File.open(file, "w+") source_file.write(source.join('')) source_file.fsync