power_assert-0.3.0/0000755000004100000410000000000012720337716014267 5ustar www-datawww-datapower_assert-0.3.0/Rakefile0000644000004100000410000000057312720337716015741 0ustar www-datawww-datarequire "bundler/gem_tasks" require "rake/testtask" task :default => :test Rake::TestTask.new do |t| # helper(simplecov) must be required before loading power_assert t.ruby_opts = ["-w", "-r./test/helper"] t.test_files = FileList["test/test_*.rb"] end desc "Run the benchmark suite" task('benchmark') do Dir.glob('benchmarks/bm_*.rb').each do |f| load(f) end end power_assert-0.3.0/Gemfile0000644000004100000410000000034612720337716015565 0ustar www-datawww-datasource "http://rubygems.org" gemspec # https://github.com/redmine/redmine/blob/3.0.4/Gemfile#L101 local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") if File.exist?(local_gemfile) eval_gemfile local_gemfile end power_assert-0.3.0/power_assert.gemspec0000644000004100000410000000210712720337716020351 0ustar www-datawww-data$:.push File.expand_path('../lib', __FILE__) require 'power_assert/version' Gem::Specification.new do |s| s.name = 'power_assert' s.version = PowerAssert::VERSION s.authors = ['Kazuki Tsujimoto'] s.email = ['kazuki@callcc.net'] s.homepage = 'https://github.com/k-tsj/power_assert' s.summary = %q{Power Assert for Ruby} s.description = %q{Power Assert for Ruby. Power Assert shows each value of variables and method calls in the expression. It is useful for testing, providing which value wasn't correct when the condition is not satisfied.} s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f) } s.require_paths = ['lib'] s.add_development_dependency 'test-unit' s.add_development_dependency 'rake' s.add_development_dependency 'simplecov' s.extra_rdoc_files = ['README.rdoc'] s.rdoc_options = ['--main', 'README.rdoc'] s.licenses = ['2-clause BSDL', "Ruby's"] end power_assert-0.3.0/README.rdoc0000644000004100000410000000141112720337716016072 0ustar www-datawww-data= power_assert == About Power Assert for Ruby. == Related Projects * {test-unit}[https://github.com/test-unit/test-unit](>= 3.0.0) * {test-unit-power_assert}[https://github.com/k-tsj/test-unit-power_assert] * {minitest-power_assert}[https://github.com/hsbt/minitest-power_assert] * {pry-power_assert}[https://github.com/yui-knk/pry-power_assert] * {rspec-power_assert}[https://github.com/joker1007/rspec-power_assert] * {power_p}[https://github.com/k-tsj/power_p] == Requirement * CRuby 2.0.0 or later == Reference * {Power Assert in Ruby (at RubyKaigi 2014) // Speaker Deck}[https://speakerdeck.com/k_tsj/power-assert-in-ruby] == Travis Build Status {}[http://travis-ci.org/k-tsj/power_assert] power_assert-0.3.0/.travis.yml0000644000004100000410000000024512720337716016401 0ustar www-datawww-datalanguage: ruby rvm: - 2.0.0-p648 - 2.1.10 - 2.2.5 - 2.3.1 - ruby-head matrix: allow_failures: - rvm: ruby-head before_install: - gem update bundler power_assert-0.3.0/lib/0000755000004100000410000000000012720337716015035 5ustar www-datawww-datapower_assert-0.3.0/lib/power_assert/0000755000004100000410000000000012720337716017552 5ustar www-datawww-datapower_assert-0.3.0/lib/power_assert/version.rb0000644000004100000410000000005312720337716021562 0ustar www-datawww-datamodule PowerAssert VERSION = "0.3.0" end power_assert-0.3.0/lib/power_assert/enable_tracepoint_events.rb0000644000004100000410000000332212720337716025141 0ustar www-datawww-dataif defined? RubyVM verbose = $VERBOSE begin $VERBOSE = nil module PowerAssert # set redefined flag basic_classes = [ Fixnum, Float, String, Array, Hash, Bignum, Symbol, Time, Regexp ] basic_operators = [ :+, :-, :*, :/, :%, :==, :===, :<, :<=, :<<, :[], :[]=, :length, :size, :empty?, :succ, :>, :>=, :!, :!=, :=~, :freeze ] class Bug11182 def fixed? true end end private_constant :Bug11182 refine Bug11182 do def fixed? end end class Bug11182Sub < Bug11182 alias _fixed? fixed? protected :_fixed? end private_constant :Bug11182Sub if (Bug11182.new.fixed? rescue false) basic_classes.each do |klass| basic_operators.each do |bop| refine(klass) do define_method(bop) {} end end end else # workaround for https://bugs.ruby-lang.org/issues/11182 basic_classes.each do |klass| basic_operators.each do |bop| if klass.public_method_defined?(bop) klass.ancestors.find {|i| i.instance_methods(false).index(bop) }.module_eval do public bop end end end end refine Symbol do def == end end end # bypass check_cfunc refine BasicObject do def ! end def == end end refine Module do def == end end end ensure $VERBOSE = verbose end # disable optimization RubyVM::InstructionSequence.compile_option = { specialized_instruction: false } end power_assert-0.3.0/lib/power_assert.rb0000644000004100000410000002504512720337716020105 0ustar www-datawww-data# power_assert.rb # # Copyright (C) 2014-2016 Kazuki Tsujimoto, All rights reserved. begin captured = false TracePoint.new(:return, :c_return) do |tp| captured = true unless tp.binding and tp.return_value raise end end.enable { __id__ } raise unless captured rescue raise LoadError, 'Fully compatible TracePoint API required' end require 'power_assert/version' require 'power_assert/enable_tracepoint_events' require 'ripper' module PowerAssert class << self def configuration @configuration ||= Configuration[false, false] end def configure yield configuration end def start(assertion_proc_or_source, assertion_method: nil, source_binding: TOPLEVEL_BINDING) if respond_to?(:clear_global_method_cache, true) clear_global_method_cache end yield Context.new(assertion_proc_or_source, assertion_method, source_binding) end private if defined?(RubyVM) def clear_global_method_cache eval('using PowerAssert.const_get(:Empty)', TOPLEVEL_BINDING) end end end Configuration = Struct.new(:lazy_inspection, :_trace_alias_method) private_constant :Configuration module Empty end private_constant :Empty class InspectedValue def initialize(value) @value = value end def inspect @value end end private_constant :InspectedValue class SafeInspectable def initialize(value) @value = value end def inspect inspected = @value.inspect if Encoding.compatible?(Encoding.default_external, inspected) inspected else begin "#{inspected.encode(Encoding.default_external)}(#{inspected.encoding})" rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError inspected.force_encoding(Encoding.default_external) end end rescue => e "InspectionFailure: #{e.class}: #{e.message.each_line.first}" end end private_constant :SafeInspectable class Context Value = Struct.new(:name, :value, :column) Ident = Struct.new(:type, :name, :column) TARGET_CALLER_DIFF = {return: 5, c_return: 4} TARGET_INDEX_OFFSET = 2 attr_reader :message_proc def initialize(assertion_proc_or_source, assertion_method, source_binding) if assertion_proc_or_source.kind_of?(Proc) @assertion_proc = assertion_proc_or_source @line = nil else @assertion_proc = source_binding.eval "Proc.new {#{assertion_proc_or_source}}" @line = assertion_proc_or_source end path = nil lineno = nil methods = nil refs = nil method_ids = nil return_values = [] @base_caller_length = -1 @assertion_method_name = assertion_method.to_s @message_proc = -> { raise RuntimeError, 'call #yield at first' if @base_caller_length < 0 @message ||= build_assertion_message(@line || '', methods || [], return_values, refs || [], @assertion_proc.binding).freeze } @proc_local_variables = @assertion_proc.binding.eval('local_variables').map(&:to_s) target_thread = Thread.current @trace_call = TracePoint.new(:call, :c_call) do |tp| next if @base_caller_length < 0 locs = caller_locations if locs.length >= @base_caller_length+TARGET_INDEX_OFFSET and Thread.current == target_thread idx = -(@base_caller_length+TARGET_INDEX_OFFSET) path = locs[idx].path lineno = locs[idx].lineno @line ||= open(path).each_line.drop(lineno - 1).first idents = extract_idents(Ripper.sexp(@line)) methods, refs = idents.partition {|i| i.type == :method } method_ids = methods.map(&:name).map(&:to_sym).each_with_object({}) {|i, h| h[i] = true } @trace_call.disable end end trace_alias_method = PowerAssert.configuration._trace_alias_method @trace = TracePoint.new(:return, :c_return) do |tp| method_id = (trace_alias_method && tp.event == :return && tp.binding.eval('::Kernel.__callee__')) || tp.method_id next if method_ids and ! method_ids[method_id] next unless tp.binding # workaround for ruby 2.2 if tp.event == :c_return loc = tp.binding.eval('[__LINE__, __FILE__]') next unless lineno == loc[0] and path == loc[1] end locs = tp.binding.eval('::Kernel.caller_locations') current_diff = locs.length - @base_caller_length if current_diff <= TARGET_CALLER_DIFF[tp.event] and Thread.current == target_thread idx = -(@base_caller_length+TARGET_INDEX_OFFSET) if path == locs[idx].path and lineno == locs[idx].lineno val = PowerAssert.configuration.lazy_inspection ? tp.return_value : InspectedValue.new(SafeInspectable.new(tp.return_value).inspect) return_values << Value[method_id.to_s, val, nil] end end end end def yield @trace_call.enable do do_yield(&@assertion_proc) end end def message @message_proc.() end private def do_yield @trace.enable do @base_caller_length = caller_locations.length yield end end def build_assertion_message(line, methods, return_values, refs, proc_binding) set_column(methods, return_values) ref_values = refs.map {|i| Value[i.name, proc_binding.eval(i.name), i.column] } vals = (return_values + ref_values).find_all(&:column).sort_by(&:column).reverse if vals.empty? return line end fmt = (0..vals[0].column).map {|i| vals.find {|v| v.column == i } ? "%<#{i}>s" : ' ' }.join lines = [] lines << line.chomp lines << sprintf(fmt, vals.each_with_object({}) {|v, h| h[v.column.to_s.to_sym] = '|' }).chomp vals.each do |i| inspected_vals = vals.each_with_object({}) do |j, h| h[j.column.to_s.to_sym] = [SafeInspectable.new(i.value).inspect, '|', ' '][i.column <=> j.column] end lines << encoding_safe_rstrip(sprintf(fmt, inspected_vals)) end lines.join("\n") end def set_column(methods, return_values) methods = methods.dup return_values.each do |val| idx = methods.index {|method| method.name == val.name } if idx val.column = methods.delete_at(idx).column end end end def encoding_safe_rstrip(str) str.rstrip rescue ArgumentError, Encoding::CompatibilityError enc = str.encoding if enc.ascii_compatible? str.b.rstrip.force_encoding(enc) else str end end def extract_idents(sexp) tag, * = sexp case tag when :arg_paren, :assoc_splat, :fcall, :hash, :method_add_block, :string_literal extract_idents(sexp[1]) when :assign, :massign extract_idents(sexp[2]) when :assoclist_from_args, :bare_assoc_hash, :dyna_symbol, :paren, :string_embexpr, :regexp_literal, :xstring_literal sexp[1].flat_map {|s| extract_idents(s) } when :assoc_new, :command, :dot2, :dot3, :string_content sexp[1..-1].flat_map {|s| extract_idents(s) } when :unary handle_columnless_ident([], sexp[1], extract_idents(sexp[2])) when :binary handle_columnless_ident(extract_idents(sexp[1]), sexp[2], extract_idents(sexp[3])) when :call if sexp[3] == :call handle_columnless_ident(extract_idents(sexp[1]), :call, []) else [sexp[1], sexp[3]].flat_map {|s| extract_idents(s) } end when :array sexp[1] ? sexp[1].flat_map {|s| extract_idents(s) } : [] when :command_call [sexp[1], sexp[4], sexp[3]].flat_map {|s| extract_idents(s) } when :aref handle_columnless_ident(extract_idents(sexp[1]), :[], extract_idents(sexp[2])) when :method_add_arg idents = extract_idents(sexp[1]) if idents.empty? # idents may be empty(e.g. ->{}.()) extract_idents(sexp[2]) else idents[0..-2] + extract_idents(sexp[2]) + [idents[-1]] end when :args_add_block _, (tag, ss0, *ss1), _ = sexp if tag == :args_add_star (ss0 + ss1).flat_map {|s| extract_idents(s) } else sexp[1].flat_map {|s| extract_idents(s) } end when :vcall _, (tag, name, (_, column)) = sexp if tag == :@ident [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]] else [] end when :program _, ((tag0, (tag1, (tag2, (tag3, mname, _)), _), (tag4, _, ss))) = sexp if tag0 == :method_add_block and tag1 == :method_add_arg and tag2 == :fcall and (tag3 == :@ident or tag3 == :@const) and mname == @assertion_method_name and (tag4 == :brace_block or tag4 == :do_block) ss.flat_map {|s| extract_idents(s) } else _, (s, *) = sexp extract_idents(s) end when :var_ref _, (tag, ref_name, (_, column)) = sexp case tag when :@kw if ref_name == 'self' [Ident[:ref, 'self', column]] else [] end when :@const, :@cvar, :@ivar, :@gvar [Ident[:ref, ref_name, column]] else [] end when :@ident, :@const _, method_name, (_, column) = sexp [Ident[:method, method_name, column]] else [] end end def str_indices(str, re, offset, limit) idx = str.index(re, offset) if idx and idx <= limit [idx, *str_indices(str, re, idx + 1, limit)] else [] end end MID2SRCTXT = { :[] => '[', :+@ => '+', :-@ => '-', :call => '(' } def handle_columnless_ident(left_idents, mid, right_idents) left_max = left_idents.max_by(&:column) right_min = right_idents.min_by(&:column) bg = left_max ? left_max.column + left_max.name.length : 0 ed = right_min ? right_min.column - 1 : @line.length - 1 mname = mid.to_s srctxt = MID2SRCTXT[mid] || mname re = / #{'\b' if /\A\w/ =~ srctxt} #{Regexp.escape(srctxt)} #{'\b' if /\w\z/ =~ srctxt} /x indices = str_indices(@line, re, bg, ed) if left_idents.empty? and right_idents.empty? left_idents + right_idents elsif left_idents.empty? left_idents + right_idents + [Ident[:method, mname, indices.last]] else left_idents + right_idents + [Ident[:method, mname, indices.first]] end end end private_constant :Context end power_assert-0.3.0/test/0000755000004100000410000000000012720337716015246 5ustar www-datawww-datapower_assert-0.3.0/test/helper.rb0000644000004100000410000000024312720337716017051 0ustar www-datawww-databegin if ENV['COVERAGE'] require 'simplecov' SimpleCov.start do add_filter '/test/' add_filter '/vendor/' end end rescue LoadError end power_assert-0.3.0/test/test_power_assert.rb0000644000004100000410000003010212720337716021343 0ustar www-datawww-datarequire 'test/unit' require 'power_assert' require 'ripper' require 'set' class TestPowerAssert < Test::Unit::TestCase class << self def t(msg='', &blk) loc = caller_locations(1, 1)[0] test("#{loc.path} --location #{loc.lineno} #{msg}", &blk) end end EXTRACT_METHODS_TEST = [ [[[:method, "c", 4], [:method, "b", 2], [:method, "d", 8], [:method, "a", 0]], 'a(b(c), d)'], [[[:method, "a", 0], [:method, "b", 2], [:method, "d", 6], [:method, "c", 4]], 'a.b.c(d)'], [[[:method, "b", 2], [:method, "a", 0], [:method, "c", 5], [:method, "e", 9], [:method, "d", 7]], 'a(b).c.d(e)'], [[[:method, "b", 4], [:method, "a", 2], [:method, "c", 7], [:method, "e", 13], [:method, "g", 11], [:method, "d", 9], [:method, "f", 0]], 'f(a(b).c.d(g(e)))'], [[[:method, "c", 5], [:method, "e", 11], [:method, "a", 0]], 'a(b: c, d: e)'], [[[:method, "b", 2], [:method, "c", 7], [:method, "d", 10], [:method, "e", 15], [:method, "a", 0]], 'a(b => c, d => e)'], [[[:method, "b", 4], [:method, "d", 10]], '{a: b, c: d}'], [[[:method, "a", 1], [:method, "b", 6], [:method, "c", 9], [:method, "d", 14]], '{a => b, c => d}'], [[[:method, "a", 2], [:method, "b", 5], [:method, "c", 10], [:method, "d", 13]], '[[a, b], [c, d]]'], [[[:method, "a", 0], [:method, "b", 2], [:method, "c", 5]], 'a b, c { d }'], [[[:method, "a", 20]], 'assertion_message { a }'], [[[:method, "a", 0]], 'a { b }'], [[[:method, "c", 4], [:method, "B", 2], [:method, "d", 8], [:method, "A", 0]], 'A(B(c), d)'], [[[:method, "c", 6], [:method, "f", 17], [:method, "h", 25], [:method, "a", 0]], 'a(b = c, (d, e = f), G = h)'], [[[:method, "b", 2], [:method, "c", 6], [:method, "d", 9], [:method, "e", 12], [:method, "g", 18], [:method, "i", 24], [:method, "j", 29], [:method, "a", 0]], 'a(b, *c, d, e, f: g, h: i, **j)'], [[[:method, "a", 0], [:method, "b", 5], [:method, "c", 9], [:method, "+", 7], [:method, "==", 2]], 'a == b + c'], [[[:ref, "var", 0], [:ref, "var", 8], [:method, "var", 4]], 'var.var(var)'], [[[:ref, "B", 2], [:ref, "@c", 5], [:ref, "@@d", 9], [:ref, "$e", 14], [:method, "f", 18], [:method, "self", 20], [:ref, "self", 26], [:method, "a", 0]], 'a(B, @c, @@d, $e, f.self, self)'], [[[:method, "a", 0], [:method, "c", 4], [:method, "b", 2]], 'a.b c'], [[[:method, "b", 4]], '"a#{b}c"'], [[[:method, "b", 4]], '/a#{b}c/'], [[], '[]'], [[[:method, "a", 0], [:method, "[]", 1]], 'a[0]'], # not supported [[], '[][]'], # not supported [[], '{}[]'], [[[:method, "a", 1], [:method, "!", 0]], '!a'], [[[:method, "a", 1], [:method, "+@", 0]], '+a'], [[[:method, "a", 1], [:method, "-@", 0]], '-a'], [[[:method, "a", 2], [:method, "!", 0], [:method, "b", 9], [:method, "+@", 8], [:method, "c", 15], [:method, "-@", 14], [:method, "==", 11], [:method, "==", 4]], '! a == (+b == -c)'], [[[:method, "b", 6]], '%x{a#{b}c}'], [[[:method, "a", 0], [:method, "b", 3]], "a..b"], [[[:method, "a", 0], [:method, "b", 4]], "a...b"], [[[:method, "b", 5]], ':"a#{b}c"'], # not supported [[], '->{}.()'], [[[:method, "a", 0], [:method, "b", 3], [:method, "call", 2]], 'a.(b)'], ] EXTRACT_METHODS_TEST.each_with_index do |(expect, source), idx| define_method("test_extract_methods_#{'%03d' % idx}") do pa = PowerAssert.const_get(:Context).new(-> { var = nil; -> { var } }.(), nil, TOPLEVEL_BINDING) pa.instance_variable_set(:@line, source) pa.instance_variable_set(:@assertion_method_name, 'assertion_message') assert_equal expect, pa.send(:extract_idents, Ripper.sexp(source)).map(&:to_a), source end end class BasicObjectSubclass < BasicObject def foo "foo" end end def assertion_message(source = nil, source_binding = TOPLEVEL_BINDING, &blk) ::PowerAssert.start(source || blk, assertion_method: __callee__, source_binding: source_binding) do |pa| pa.yield pa.message end end def Assertion(&blk) ::PowerAssert.start(blk, assertion_method: __callee__) do |pa| pa.yield pa.message end end define_method(:bmethod) do false end sub_test_case 'lazy_inspection' do t do PowerAssert.configure do |c| assert !c.lazy_inspection end assert_equal < | | 3 | false String END "0".class == "3".to_i.times.map {|i| i + 1 }.class } end t do assert_equal '', assertion_message { false } end t do assert_equal < | | | Set | | false | # Set END Set.new == Set.new([0]) } end t do var = [10,20] assert_equal <:.*> # END assertion_message { @o.new.alias_of_iseq } end t do omit 'alias of cfunc is not supported yet' assert_match Regexp.new(<:.*> | #<#:.*> # END assertion_message { @o.new.alias_of_cfunc } end end sub_test_case 'assertion_message_with_incompatible_encodings' do if Encoding.default_external == Encoding::UTF_8 t do a = "\u3042" def a.inspect super.encode('utf-16le') end assert_equal <