require "rspec/power_assert/version" require "rspec/core" require "power_assert" module PowerAssert INTERNAL_LIB_DIRS[RSpec::PowerAssert] = File.dirname(caller_locations(1, 1).first.path) end module RSpec module PowerAssert @verbose_successful_report = true def self.verbose_successful_report !!@verbose_successful_report end def self.verbose_successful_report=(verbose) @verbose_successful_report = verbose end def self.example_assertion_alias(name) alias_method(name.to_sym, :is_asserted_by) end def self.example_group_assertion_alias(name) PowerAssertExtensions.assertion_method_alias(name) end def is_asserted_by(&blk) result, msg = ::PowerAssert.start(blk, assertion_method: __callee__) do |tp| [tp.yield, tp.message_proc.call] end location = blk.source_location.join(":") handle_result_and_message(result, msg, __callee__, location) end private def evaluate_example(method_name, &blk) # hack for context changing pr = -> { self.instance_exec(&blk) } result, msg = ::PowerAssert.start(pr, assertion_method: method_name) do |tp| [tp.yield, tp.message_proc.call] end location = blk.source_location.join(":") handle_result_and_message(result, msg, method_name, location) end def handle_result_and_message(result, msg, method_name, location) if result RSpec::Matchers.last_matcher = DummyAssertionMatcher.new(msg, method_name.to_s) if RSpec::Matchers.respond_to?(:last_should=) RSpec::Matchers.last_should = :should # for RSpec 2 else RSpec::Matchers.last_expectation_handler = DummyExpectationHandler # for RSpec 3 end else ex = RSpec::Expectations::ExpectationNotMetError.new(msg) if defined?(RSpec::Support) && RSpec::Support.respond_to?(:notify_failure) # for RSpec 3.3+ RSpec::Support.notify_failure(ex) else # for RSpec 2, 3.0, 3.1, 3.2 ex.set_backtrace(location) raise ex end end end # for generating description class DummyExpectationHandler def self.verb "should" end end private_constant :DummyExpectationHandler class DummyAssertionMatcher INDENT_LEVEL = 12 def initialize(msg, method_name) @msg = msg @assertion_method = method_name end def description if @msg =~ /^(\s*)#{@assertion_method}/ output = @msg.sub(/^(\s*#{@assertion_method})/, "#{display_message}") offset = display_message.length - $1.length else output = @msg.sub(/^(\s*)(\S.*?)$/, "#{display_message} { \\2 }") offset = display_message.length + 3 - $1.length end "#{$/}#{build_message(output, offset)}" end private def build_message(output, offset) if RSpec::PowerAssert.verbose_successful_report output.each_line.with_index.map do |l, idx| next l if idx == 0 if offset > 1 " " * offset + l else l.each_char.drop(offset.abs).join end end.join else output.each_line.first end end def display_message " " * INDENT_LEVEL + "be asserted by" end end private_constant :DummyAssertionMatcher end module PowerAssertExtensions def self.assertion_method_alias(name) alias_method name.to_sym, :it_is_asserted_by end def it_is_asserted_by(description = nil, &blk) file, _lineno = blk.source_location backtrace = caller.drop_while {|l| l !~ /#{Regexp.escape(file)}/} it description, caller: backtrace do evaluate_example(__callee__, &blk) end end end end RSpec.configure do |config| config.extend RSpec::PowerAssertExtensions config.include RSpec::PowerAssert end