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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d2eae14f4d92ca09fbbb32496eb627f1d1451c2d
4
- data.tar.gz: 8d85402b050df02cb8965870edeadf02a76cbb7f
3
+ metadata.gz: e119019e6ff63c30a7d70942bfc554915bfeefd5
4
+ data.tar.gz: 7b06c77145a97c59f99b88fcdd926fcb24360138
5
5
  SHA512:
6
- metadata.gz: 3d7f505cd68804f219e1f352c52b1b30b2d08ca2ce04e14c327fc7ed48703a0c68cc9e9a2109172f5ec31a350d3ecb821dad77e02da1c636aa066f8949b3215f
7
- data.tar.gz: 635290bfb734739a5c63a40b225e2cca422f142e132e92c208f248b940742fa7734bda5663a616415f9f3ac68c87c015b3d3db8342e052116dc5ce0289b9f4bd
6
+ metadata.gz: 7a7b1d0f33ab25403d60820f9bdfd9fb4005a0e3653eb783c2c4a7a705fbec5b3efa6c7a6740416c26255c02981e848f3484c448de5236f9e71a1e089a5a16ba
7
+ data.tar.gz: 9a3cb5c8ecffa00c64e02f5edc7477f6af41fc196aec082946b6f155ff63fd5bdc1e7027184c95cfdd49e0705afc0163c492903da08f5f65e1d87c41c22b313c
data/.travis.yml CHANGED
@@ -1,9 +1,11 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  rvm:
3
4
  - 2.0.0-p648
4
5
  - 2.1.10
5
6
  - 2.2.6
6
7
  - 2.3.3
8
+ - 2.4.0
7
9
  - ruby-head
8
10
  matrix:
9
11
  allow_failures:
data/BSDL CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (C) 2014-2016 Kazuki Tsujimoto, All rights reserved.
1
+ Copyright (C) 2014-2017 Kazuki Tsujimoto, All rights reserved.
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without
4
4
  modification, are permitted provided that the following conditions
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (C) 2014-2016 Kazuki Tsujimoto, All rights reserved.
1
+ Copyright (C) 2014-2017 Kazuki Tsujimoto, All rights reserved.
2
2
 
3
3
  You can redistribute it and/or modify it under either the terms of the
4
4
  2-clause BSDL (see the file BSDL), or the conditions below:
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/helper"]
8
- t.test_files = FileList["test/test_*.rb"]
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
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'power_assert'
5
+
6
+ begin
7
+ require 'pry'
8
+ Pry
9
+ rescue LoadError
10
+ require 'irb'
11
+ IRB
12
+ end.start
data/bin/setup ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
data/lib/power_assert.rb CHANGED
@@ -1,326 +1,81 @@
1
1
  # power_assert.rb
2
2
  #
3
- # Copyright (C) 2014-2016 Kazuki Tsujimoto, All rights reserved.
3
+ # Copyright (C) 2014-2017 Kazuki Tsujimoto, All rights reserved.
4
4
 
5
5
  begin
6
- captured = false
7
- TracePoint.new(:return, :c_return) do |tp|
8
- captured = true
9
- unless tp.binding and tp.return_value
10
- raise
11
- end
12
- end.enable { __id__ }
13
- raise unless captured
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/enable_tracepoint_events'
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 Context.new(assertion_proc_or_source, assertion_method, source_binding)
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 initialize(assertion_proc_or_source, assertion_method, source_binding)
88
- if assertion_proc_or_source.kind_of?(Proc)
89
- @assertion_proc = assertion_proc_or_source
90
- @line = nil
91
- else
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 yield
147
- @trace_call.enable do
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 message
153
- @message_proc.()
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 do_yield
159
- @trace.enable do
160
- @base_caller_length = caller_locations.length
161
- yield
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 build_assertion_message(line, methods, return_values, refs, proc_binding)
166
- set_column(methods, return_values)
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
- def set_column(methods, return_values)
186
- methods = methods.dup
187
- return_values.each do |val|
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
- MID2SRCTXT = {
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 :Context
80
+ private_constant :Empty
326
81
  end