power_assert 0.4.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|