power_assert 0.4.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/BSDL +1 -1
- data/COPYING +1 -1
- data/README.rdoc +1 -0
- data/Rakefile +12 -4
- data/bin/console +12 -0
- data/bin/setup +6 -0
- data/lib/power_assert.rb +43 -288
- data/lib/power_assert/colorize.rb +9 -0
- data/lib/power_assert/configuration.rb +25 -3
- data/lib/power_assert/context.rb +201 -0
- data/lib/power_assert/enable_tracepoint_events.rb +6 -1
- data/lib/power_assert/inspector.rb +61 -0
- data/lib/power_assert/parser.rb +239 -0
- data/lib/power_assert/version.rb +1 -1
- data/power_assert.gemspec +12 -5
- metadata +52 -8
- data/benchmarks/bm_yhpg.rb +0 -59
- data/benchmarks/helper.rb +0 -8
- data/test/helper.rb +0 -10
- data/test/test_power_assert.rb +0 -514
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e119019e6ff63c30a7d70942bfc554915bfeefd5
|
4
|
+
data.tar.gz: 7b06c77145a97c59f99b88fcdd926fcb24360138
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a7b1d0f33ab25403d60820f9bdfd9fb4005a0e3653eb783c2c4a7a705fbec5b3efa6c7a6740416c26255c02981e848f3484c448de5236f9e71a1e089a5a16ba
|
7
|
+
data.tar.gz: 9a3cb5c8ecffa00c64e02f5edc7477f6af41fc196aec082946b6f155ff63fd5bdc1e7027184c95cfdd49e0705afc0163c492903da08f5f65e1d87c41c22b313c
|
data/.travis.yml
CHANGED
data/BSDL
CHANGED
data/COPYING
CHANGED
data/README.rdoc
CHANGED
@@ -9,6 +9,7 @@ Power Assert for Ruby.
|
|
9
9
|
* {pry-power_assert}[https://github.com/yui-knk/pry-power_assert]
|
10
10
|
* {rspec-power_assert}[https://github.com/joker1007/rspec-power_assert]
|
11
11
|
* {power_p}[https://github.com/k-tsj/power_p]
|
12
|
+
* {pry-byebug-power_assert}[https://github.com/k-tsj/pry-byebug-power_assert]
|
12
13
|
|
13
14
|
== Requirement
|
14
15
|
* CRuby 2.0.0 or later
|
data/Rakefile
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
|
3
2
|
require "rake/testtask"
|
3
|
+
|
4
4
|
task :default => :test
|
5
|
-
Rake::TestTask.new do |t|
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
6
|
# helper(simplecov) must be required before loading power_assert
|
7
|
-
t.ruby_opts = ["-w", "-r./test/
|
8
|
-
t.test_files = FileList["test
|
7
|
+
t.ruby_opts = ["-w", "-r./test/test_helper"]
|
8
|
+
t.test_files = FileList["test/**/*_test.rb"].exclude do |i|
|
9
|
+
begin
|
10
|
+
return false unless defined?(RubyVM)
|
11
|
+
RubyVM::InstructionSequence.compile(open(i).read)
|
12
|
+
false
|
13
|
+
rescue SyntaxError
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
9
17
|
end
|
10
18
|
|
11
19
|
desc "Run the benchmark suite"
|
data/bin/console
ADDED
data/bin/setup
ADDED
data/lib/power_assert.rb
CHANGED
@@ -1,326 +1,81 @@
|
|
1
1
|
# power_assert.rb
|
2
2
|
#
|
3
|
-
# Copyright (C) 2014-
|
3
|
+
# Copyright (C) 2014-2017 Kazuki Tsujimoto, All rights reserved.
|
4
4
|
|
5
5
|
begin
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
unless defined?(Byebug)
|
7
|
+
captured = false
|
8
|
+
TracePoint.new(:return, :c_return) do |tp|
|
9
|
+
captured = true
|
10
|
+
unless tp.binding and tp.return_value
|
11
|
+
raise ''
|
12
|
+
end
|
13
|
+
end.enable { __id__ }
|
14
|
+
raise '' unless captured
|
15
|
+
end
|
14
16
|
rescue
|
15
17
|
raise LoadError, 'Fully compatible TracePoint API required'
|
16
18
|
end
|
17
19
|
|
18
20
|
require 'power_assert/version'
|
19
21
|
require 'power_assert/configuration'
|
20
|
-
require 'power_assert/
|
21
|
-
require 'ripper'
|
22
|
+
require 'power_assert/context'
|
22
23
|
|
23
24
|
module PowerAssert
|
25
|
+
POWER_ASSERT_LIB_DIR = __dir__
|
26
|
+
IGNORED_LIB_DIRS = {PowerAssert => POWER_ASSERT_LIB_DIR}
|
27
|
+
private_constant :POWER_ASSERT_LIB_DIR, :IGNORED_LIB_DIRS
|
28
|
+
|
24
29
|
class << self
|
25
30
|
def start(assertion_proc_or_source, assertion_method: nil, source_binding: TOPLEVEL_BINDING)
|
26
31
|
if respond_to?(:clear_global_method_cache, true)
|
27
32
|
clear_global_method_cache
|
28
33
|
end
|
29
|
-
yield
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
if defined?(RubyVM)
|
35
|
-
def clear_global_method_cache
|
36
|
-
eval('using PowerAssert.const_get(:Empty)', TOPLEVEL_BINDING)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
module Empty
|
42
|
-
end
|
43
|
-
private_constant :Empty
|
44
|
-
|
45
|
-
class InspectedValue
|
46
|
-
def initialize(value)
|
47
|
-
@value = value
|
48
|
-
end
|
49
|
-
|
50
|
-
def inspect
|
51
|
-
@value
|
52
|
-
end
|
53
|
-
end
|
54
|
-
private_constant :InspectedValue
|
55
|
-
|
56
|
-
class SafeInspectable
|
57
|
-
def initialize(value)
|
58
|
-
@value = value
|
59
|
-
end
|
60
|
-
|
61
|
-
def inspect
|
62
|
-
inspected = @value.inspect
|
63
|
-
if Encoding.compatible?(Encoding.default_external, inspected)
|
64
|
-
inspected
|
65
|
-
else
|
66
|
-
begin
|
67
|
-
"#{inspected.encode(Encoding.default_external)}(#{inspected.encoding})"
|
68
|
-
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
|
69
|
-
inspected.force_encoding(Encoding.default_external)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
rescue => e
|
73
|
-
"InspectionFailure: #{e.class}: #{e.message.each_line.first}"
|
34
|
+
yield BlockContext.new(assertion_proc_or_source, assertion_method, source_binding)
|
74
35
|
end
|
75
|
-
end
|
76
|
-
private_constant :SafeInspectable
|
77
|
-
|
78
|
-
class Context
|
79
|
-
Value = Struct.new(:name, :value, :column)
|
80
|
-
Ident = Struct.new(:type, :name, :column)
|
81
|
-
|
82
|
-
TARGET_CALLER_DIFF = {return: 5, c_return: 4}
|
83
|
-
TARGET_INDEX_OFFSET = 2
|
84
|
-
|
85
|
-
attr_reader :message_proc
|
86
36
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@assertion_proc = source_binding.eval "Proc.new {#{assertion_proc_or_source}}"
|
93
|
-
@line = assertion_proc_or_source
|
94
|
-
end
|
95
|
-
path = nil
|
96
|
-
lineno = nil
|
97
|
-
methods = nil
|
98
|
-
refs = nil
|
99
|
-
method_ids = nil
|
100
|
-
return_values = []
|
101
|
-
@base_caller_length = -1
|
102
|
-
@assertion_method_name = assertion_method.to_s
|
103
|
-
@message_proc = -> {
|
104
|
-
raise RuntimeError, 'call #yield at first' if @base_caller_length < 0
|
105
|
-
@message ||= build_assertion_message(@line || '', methods || [], return_values, refs || [], @assertion_proc.binding).freeze
|
106
|
-
}
|
107
|
-
@proc_local_variables = @assertion_proc.binding.eval('local_variables').map(&:to_s)
|
108
|
-
target_thread = Thread.current
|
109
|
-
@trace_call = TracePoint.new(:call, :c_call) do |tp|
|
110
|
-
next if @base_caller_length < 0
|
111
|
-
locs = caller_locations
|
112
|
-
if locs.length >= @base_caller_length+TARGET_INDEX_OFFSET and Thread.current == target_thread
|
113
|
-
idx = -(@base_caller_length+TARGET_INDEX_OFFSET)
|
114
|
-
path = locs[idx].path
|
115
|
-
lineno = locs[idx].lineno
|
116
|
-
@line ||= open(path).each_line.drop(lineno - 1).first
|
117
|
-
idents = extract_idents(Ripper.sexp(@line))
|
118
|
-
methods, refs = idents.partition {|i| i.type == :method }
|
119
|
-
method_ids = methods.map(&:name).map(&:to_sym).each_with_object({}) {|i, h| h[i] = true }
|
120
|
-
@trace_call.disable
|
121
|
-
end
|
122
|
-
end
|
123
|
-
trace_alias_method = PowerAssert.configuration._trace_alias_method
|
124
|
-
@trace = TracePoint.new(:return, :c_return) do |tp|
|
125
|
-
method_id = SUPPORT_ALIAS_METHOD ? tp.callee_id :
|
126
|
-
trace_alias_method && tp.event == :return ? tp.binding.eval('::Kernel.__callee__') :
|
127
|
-
tp.method_id
|
128
|
-
next if method_ids and ! method_ids[method_id]
|
129
|
-
next if tp.event == :c_return and
|
130
|
-
not (lineno == tp.lineno and path == tp.path)
|
131
|
-
next unless tp.binding # workaround for ruby 2.2
|
132
|
-
locs = tp.binding.eval('::Kernel.caller_locations')
|
133
|
-
current_diff = locs.length - @base_caller_length
|
134
|
-
if current_diff <= TARGET_CALLER_DIFF[tp.event] and Thread.current == target_thread
|
135
|
-
idx = -(@base_caller_length+TARGET_INDEX_OFFSET)
|
136
|
-
if path == locs[idx].path and lineno == locs[idx].lineno
|
137
|
-
val = PowerAssert.configuration.lazy_inspection ?
|
138
|
-
tp.return_value :
|
139
|
-
InspectedValue.new(SafeInspectable.new(tp.return_value).inspect)
|
140
|
-
return_values << Value[method_id.to_s, val, nil]
|
141
|
-
end
|
142
|
-
end
|
37
|
+
def trace(frame)
|
38
|
+
begin
|
39
|
+
raise 'Byebug is not started yet' unless Byebug.started?
|
40
|
+
rescue NameError
|
41
|
+
raise "PowerAssert.#{__method__} requires Byebug"
|
143
42
|
end
|
43
|
+
ctx = TraceContext.new(frame._binding)
|
44
|
+
ctx.enable
|
45
|
+
ctx
|
144
46
|
end
|
145
47
|
|
146
|
-
def
|
147
|
-
|
148
|
-
do_yield(&@assertion_proc)
|
149
|
-
end
|
48
|
+
def app_caller_locations
|
49
|
+
caller_locations.drop_while {|i| ignored_file?(i.path) }.take_while {|i| ! ignored_file?(i.path) }
|
150
50
|
end
|
151
51
|
|
152
|
-
def
|
153
|
-
|
52
|
+
def app_context?
|
53
|
+
top_frame = caller_locations.drop_while {|i| i.path.start_with?(POWER_ASSERT_LIB_DIR) }.first
|
54
|
+
top_frame && ! ignored_file?(top_frame.path)
|
154
55
|
end
|
155
56
|
|
156
57
|
private
|
157
58
|
|
158
|
-
def
|
159
|
-
|
160
|
-
|
161
|
-
|
59
|
+
def ignored_file?(file)
|
60
|
+
IGNORED_LIB_DIRS[Byebug] = lib_dir(Byebug, :load_settings, 2) if defined?(Byebug) and ! IGNORED_LIB_DIRS[Byebug]
|
61
|
+
IGNORED_LIB_DIRS[PryByebug] = lib_dir(Pry, :start_with_pry_byebug, 2) if defined?(PryByebug) and ! IGNORED_LIB_DIRS[PryByebug]
|
62
|
+
IGNORED_LIB_DIRS.find do |_, dir|
|
63
|
+
file.start_with?(dir)
|
162
64
|
end
|
163
65
|
end
|
164
66
|
|
165
|
-
def
|
166
|
-
|
167
|
-
ref_values = refs.map {|i| Value[i.name, proc_binding.eval(i.name), i.column] }
|
168
|
-
vals = (return_values + ref_values).find_all(&:column).sort_by(&:column).reverse
|
169
|
-
if vals.empty?
|
170
|
-
return line
|
171
|
-
end
|
172
|
-
fmt = (0..vals[0].column).map {|i| vals.find {|v| v.column == i } ? "%<#{i}>s" : ' ' }.join
|
173
|
-
lines = []
|
174
|
-
lines << line.chomp
|
175
|
-
lines << sprintf(fmt, vals.each_with_object({}) {|v, h| h[v.column.to_s.to_sym] = '|' }).chomp
|
176
|
-
vals.each do |i|
|
177
|
-
inspected_vals = vals.each_with_object({}) do |j, h|
|
178
|
-
h[j.column.to_s.to_sym] = [SafeInspectable.new(i.value).inspect, '|', ' '][i.column <=> j.column]
|
179
|
-
end
|
180
|
-
lines << encoding_safe_rstrip(sprintf(fmt, inspected_vals))
|
181
|
-
end
|
182
|
-
lines.join("\n")
|
67
|
+
def lib_dir(obj, mid, depth)
|
68
|
+
File.expand_path('../' * depth, obj.method(mid).source_location[0])
|
183
69
|
end
|
184
70
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
idx = methods.index {|method| method.name == val.name }
|
189
|
-
if idx
|
190
|
-
val.column = methods.delete_at(idx).column
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def encoding_safe_rstrip(str)
|
196
|
-
str.rstrip
|
197
|
-
rescue ArgumentError, Encoding::CompatibilityError
|
198
|
-
enc = str.encoding
|
199
|
-
if enc.ascii_compatible?
|
200
|
-
str.b.rstrip.force_encoding(enc)
|
201
|
-
else
|
202
|
-
str
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def extract_idents(sexp)
|
207
|
-
tag, * = sexp
|
208
|
-
case tag
|
209
|
-
when :arg_paren, :assoc_splat, :fcall, :hash, :method_add_block, :string_literal
|
210
|
-
extract_idents(sexp[1])
|
211
|
-
when :assign, :massign
|
212
|
-
extract_idents(sexp[2])
|
213
|
-
when :assoclist_from_args, :bare_assoc_hash, :dyna_symbol, :paren, :string_embexpr,
|
214
|
-
:regexp_literal, :xstring_literal
|
215
|
-
sexp[1].flat_map {|s| extract_idents(s) }
|
216
|
-
when :assoc_new, :command, :dot2, :dot3, :string_content
|
217
|
-
sexp[1..-1].flat_map {|s| extract_idents(s) }
|
218
|
-
when :unary
|
219
|
-
handle_columnless_ident([], sexp[1], extract_idents(sexp[2]))
|
220
|
-
when :binary
|
221
|
-
handle_columnless_ident(extract_idents(sexp[1]), sexp[2], extract_idents(sexp[3]))
|
222
|
-
when :call
|
223
|
-
if sexp[3] == :call
|
224
|
-
handle_columnless_ident(extract_idents(sexp[1]), :call, [])
|
225
|
-
else
|
226
|
-
[sexp[1], sexp[3]].flat_map {|s| extract_idents(s) }
|
227
|
-
end
|
228
|
-
when :array
|
229
|
-
sexp[1] ? sexp[1].flat_map {|s| extract_idents(s) } : []
|
230
|
-
when :command_call
|
231
|
-
[sexp[1], sexp[4], sexp[3]].flat_map {|s| extract_idents(s) }
|
232
|
-
when :aref
|
233
|
-
handle_columnless_ident(extract_idents(sexp[1]), :[], extract_idents(sexp[2]))
|
234
|
-
when :method_add_arg
|
235
|
-
idents = extract_idents(sexp[1])
|
236
|
-
if idents.empty?
|
237
|
-
# idents may be empty(e.g. ->{}.())
|
238
|
-
extract_idents(sexp[2])
|
239
|
-
else
|
240
|
-
idents[0..-2] + extract_idents(sexp[2]) + [idents[-1]]
|
241
|
-
end
|
242
|
-
when :args_add_block
|
243
|
-
_, (tag, ss0, *ss1), _ = sexp
|
244
|
-
if tag == :args_add_star
|
245
|
-
(ss0 + ss1).flat_map {|s| extract_idents(s) }
|
246
|
-
else
|
247
|
-
sexp[1].flat_map {|s| extract_idents(s) }
|
248
|
-
end
|
249
|
-
when :vcall
|
250
|
-
_, (tag, name, (_, column)) = sexp
|
251
|
-
if tag == :@ident
|
252
|
-
[Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]]
|
253
|
-
else
|
254
|
-
[]
|
255
|
-
end
|
256
|
-
when :program
|
257
|
-
_, ((tag0, (tag1, (tag2, (tag3, mname, _)), _), (tag4, _, ss))) = sexp
|
258
|
-
if tag0 == :method_add_block and tag1 == :method_add_arg and tag2 == :fcall and
|
259
|
-
(tag3 == :@ident or tag3 == :@const) and mname == @assertion_method_name and (tag4 == :brace_block or tag4 == :do_block)
|
260
|
-
ss.flat_map {|s| extract_idents(s) }
|
261
|
-
else
|
262
|
-
_, (s, *) = sexp
|
263
|
-
extract_idents(s)
|
264
|
-
end
|
265
|
-
when :var_ref
|
266
|
-
_, (tag, ref_name, (_, column)) = sexp
|
267
|
-
case tag
|
268
|
-
when :@kw
|
269
|
-
if ref_name == 'self'
|
270
|
-
[Ident[:ref, 'self', column]]
|
271
|
-
else
|
272
|
-
[]
|
273
|
-
end
|
274
|
-
when :@const, :@cvar, :@ivar, :@gvar
|
275
|
-
[Ident[:ref, ref_name, column]]
|
276
|
-
else
|
277
|
-
[]
|
278
|
-
end
|
279
|
-
when :@ident, :@const
|
280
|
-
_, method_name, (_, column) = sexp
|
281
|
-
[Ident[:method, method_name, column]]
|
282
|
-
else
|
283
|
-
[]
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def str_indices(str, re, offset, limit)
|
288
|
-
idx = str.index(re, offset)
|
289
|
-
if idx and idx <= limit
|
290
|
-
[idx, *str_indices(str, re, idx + 1, limit)]
|
291
|
-
else
|
292
|
-
[]
|
71
|
+
if defined?(RubyVM)
|
72
|
+
def clear_global_method_cache
|
73
|
+
eval('using PowerAssert.const_get(:Empty)', TOPLEVEL_BINDING)
|
293
74
|
end
|
294
75
|
end
|
76
|
+
end
|
295
77
|
|
296
|
-
|
297
|
-
:[] => '[',
|
298
|
-
:+@ => '+',
|
299
|
-
:-@ => '-',
|
300
|
-
:call => '('
|
301
|
-
}
|
302
|
-
|
303
|
-
def handle_columnless_ident(left_idents, mid, right_idents)
|
304
|
-
left_max = left_idents.max_by(&:column)
|
305
|
-
right_min = right_idents.min_by(&:column)
|
306
|
-
bg = left_max ? left_max.column + left_max.name.length : 0
|
307
|
-
ed = right_min ? right_min.column - 1 : @line.length - 1
|
308
|
-
mname = mid.to_s
|
309
|
-
srctxt = MID2SRCTXT[mid] || mname
|
310
|
-
re = /
|
311
|
-
#{'\b' if /\A\w/ =~ srctxt}
|
312
|
-
#{Regexp.escape(srctxt)}
|
313
|
-
#{'\b' if /\w\z/ =~ srctxt}
|
314
|
-
/x
|
315
|
-
indices = str_indices(@line, re, bg, ed)
|
316
|
-
if left_idents.empty? and right_idents.empty?
|
317
|
-
left_idents + right_idents
|
318
|
-
elsif left_idents.empty?
|
319
|
-
left_idents + right_idents + [Ident[:method, mname, indices.last]]
|
320
|
-
else
|
321
|
-
left_idents + right_idents + [Ident[:method, mname, indices.first]]
|
322
|
-
end
|
323
|
-
end
|
78
|
+
module Empty
|
324
79
|
end
|
325
|
-
private_constant :
|
80
|
+
private_constant :Empty
|
326
81
|
end
|