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 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