power_assert-0.3.0/ 0000755 0000041 0000041 00000000000 12720337716 014267 5 ustar www-data www-data power_assert-0.3.0/Rakefile 0000644 0000041 0000041 00000000573 12720337716 015741 0 ustar www-data www-data require "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/Gemfile 0000644 0000041 0000041 00000000346 12720337716 015565 0 ustar www-data www-data source "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.gemspec 0000644 0000041 0000041 00000002107 12720337716 020351 0 ustar www-data www-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.rdoc 0000644 0000041 0000041 00000001411 12720337716 016072 0 ustar www-data www-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.yml 0000644 0000041 0000041 00000000245 12720337716 016401 0 ustar www-data www-data language: 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/ 0000755 0000041 0000041 00000000000 12720337716 015035 5 ustar www-data www-data power_assert-0.3.0/lib/power_assert/ 0000755 0000041 0000041 00000000000 12720337716 017552 5 ustar www-data www-data power_assert-0.3.0/lib/power_assert/version.rb 0000644 0000041 0000041 00000000053 12720337716 021562 0 ustar www-data www-data module PowerAssert
VERSION = "0.3.0"
end
power_assert-0.3.0/lib/power_assert/enable_tracepoint_events.rb 0000644 0000041 0000041 00000003322 12720337716 025141 0 ustar www-data www-data if 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.rb 0000644 0000041 0000041 00000025045 12720337716 020105 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 12720337716 015246 5 ustar www-data www-data power_assert-0.3.0/test/helper.rb 0000644 0000041 0000041 00000000243 12720337716 017051 0 ustar www-data www-data begin
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.rb 0000644 0000041 0000041 00000030102 12720337716 021343 0 ustar www-data www-data require '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 <